Fix reports and charts for #508

This commit is contained in:
James Cole
2016-12-30 08:41:48 +01:00
parent 13f6bd759b
commit 02bbdcc251
14 changed files with 208 additions and 254 deletions

View File

@@ -14,6 +14,7 @@ namespace FireflyIII\Helpers\Collection;
use Carbon\Carbon; use Carbon\Carbon;
use FireflyIII\Models\Budget as BudgetModel; use FireflyIII\Models\Budget as BudgetModel;
use FireflyIII\Models\BudgetLimit;
use Illuminate\Support\Collection; use Illuminate\Support\Collection;
/** /**
@@ -34,12 +35,27 @@ class BalanceLine
/** @var BudgetModel */ /** @var BudgetModel */
protected $budget; protected $budget;
/** @var Carbon */
protected $endDate;
/** @var int */ /** @var int */
protected $role = self::ROLE_DEFAULTROLE; protected $role = self::ROLE_DEFAULTROLE;
/** @var Carbon */
protected $startDate; /** @var BudgetLimit */
protected $budgetLimit;
/**
* @return BudgetLimit
*/
public function getBudgetLimit(): BudgetLimit
{
return $this->budgetLimit;
}
/**
* @param BudgetLimit $budgetLimit
*/
public function setBudgetLimit(BudgetLimit $budgetLimit)
{
$this->budgetLimit = $budgetLimit;
}
/** /**
* *
@@ -95,15 +111,7 @@ class BalanceLine
*/ */
public function getEndDate() public function getEndDate()
{ {
return $this->endDate; return $this->budgetLimit->end_date ?? new Carbon;
}
/**
* @param Carbon $endDate
*/
public function setEndDate($endDate)
{
$this->endDate = $endDate;
} }
/** /**
@@ -127,15 +135,7 @@ class BalanceLine
*/ */
public function getStartDate() public function getStartDate()
{ {
return $this->startDate; return $this->budgetLimit->start_date ?? new Carbon;
}
/**
* @param Carbon $startDate
*/
public function setStartDate($startDate)
{
$this->startDate = $startDate;
} }
/** /**
@@ -170,7 +170,7 @@ class BalanceLine
*/ */
public function leftOfRepetition(): string public function leftOfRepetition(): string
{ {
$start = $this->budget->amount ?? '0'; $start = $this->budgetLimit->amount ?? '0';
/** @var BalanceEntry $balanceEntry */ /** @var BalanceEntry $balanceEntry */
foreach ($this->getBalanceEntries() as $balanceEntry) { foreach ($this->getBalanceEntries() as $balanceEntry) {
$start = bcadd($balanceEntry->getSpent(), $start); $start = bcadd($balanceEntry->getSpent(), $start);

View File

@@ -13,7 +13,7 @@ declare(strict_types = 1);
namespace FireflyIII\Helpers\Collection; namespace FireflyIII\Helpers\Collection;
use FireflyIII\Models\Budget as BudgetModel; use FireflyIII\Models\Budget as BudgetModel;
use FireflyIII\Models\LimitRepetition; use FireflyIII\Models\BudgetLimit;
/** /**
* *
@@ -26,14 +26,14 @@ class BudgetLine
/** @var BudgetModel */ /** @var BudgetModel */
protected $budget; protected $budget;
/** @var BudgetLimit */
protected $budgetLimit;
/** @var string */ /** @var string */
protected $budgeted = '0'; protected $budgeted = '0';
/** @var string */ /** @var string */
protected $left = '0'; protected $left = '0';
/** @var string */ /** @var string */
protected $overspent = '0'; protected $overspent = '0';
/** @var LimitRepetition */
protected $repetition;
/** @var string */ /** @var string */
protected $spent = '0'; protected $spent = '0';
@@ -57,6 +57,26 @@ class BudgetLine
return $this; return $this;
} }
/**
* @return BudgetLimit
*/
public function getBudgetLimit(): BudgetLimit
{
return $this->budgetLimit ?? new BudgetLimit;
}
/**
* @param BudgetLimit $budgetLimit
*
* @return BudgetLimit
*/
public function setBudgetLimit(BudgetLimit $budgetLimit): BudgetLine
{
$this->budgetLimit = $budgetLimit;
return $this;
}
/** /**
* @return string * @return string
*/ */
@@ -117,26 +137,6 @@ class BudgetLine
return $this; return $this;
} }
/**
* @return LimitRepetition
*/
public function getRepetition(): LimitRepetition
{
return $this->repetition ?? new LimitRepetition;
}
/**
* @param LimitRepetition $repetition
*
* @return BudgetLine
*/
public function setRepetition(LimitRepetition $repetition): BudgetLine
{
$this->repetition = $repetition;
return $this;
}
/** /**
* @return string * @return string
*/ */

View File

@@ -19,8 +19,7 @@ use FireflyIII\Helpers\Collection\Balance;
use FireflyIII\Helpers\Collection\BalanceEntry; use FireflyIII\Helpers\Collection\BalanceEntry;
use FireflyIII\Helpers\Collection\BalanceHeader; use FireflyIII\Helpers\Collection\BalanceHeader;
use FireflyIII\Helpers\Collection\BalanceLine; use FireflyIII\Helpers\Collection\BalanceLine;
use FireflyIII\Models\Budget; use FireflyIII\Models\BudgetLimit;
use FireflyIII\Models\LimitRepetition;
use FireflyIII\Models\Tag; use FireflyIII\Models\Tag;
use FireflyIII\Models\TransactionType; use FireflyIII\Models\TransactionType;
use FireflyIII\Repositories\Budget\BudgetRepositoryInterface; use FireflyIII\Repositories\Budget\BudgetRepositoryInterface;
@@ -62,19 +61,17 @@ class BalanceReportHelper implements BalanceReportHelperInterface
public function getBalanceReport(Collection $accounts, Carbon $start, Carbon $end): Balance public function getBalanceReport(Collection $accounts, Carbon $start, Carbon $end): Balance
{ {
Log::debug('Start of balance report'); Log::debug('Start of balance report');
$balance = new Balance; $balance = new Balance;
$header = new BalanceHeader; $header = new BalanceHeader;
$limitRepetitions = $this->budgetRepository->getAllBudgetLimitRepetitions($start, $end); $budgetLimits = $this->budgetRepository->getAllBudgetLimits($start, $end);
foreach ($accounts as $account) { foreach ($accounts as $account) {
Log::debug(sprintf('Add account %s to headers.', $account->name)); Log::debug(sprintf('Add account %s to headers.', $account->name));
$header->addAccount($account); $header->addAccount($account);
} }
/** @var LimitRepetition $repetition */ /** @var BudgetLimit $budgetLimit */
foreach ($limitRepetitions as $repetition) { foreach ($budgetLimits as $budgetLimit) {
$budget = $this->budgetRepository->find($repetition->budget_id); $line = $this->createBalanceLine($budgetLimit, $accounts);
Log::debug(sprintf('Create balance line for budget #%d ("%s") and repetition #%d', $budget->id, $budget->name, $repetition->id));
$line = $this->createBalanceLine($budget, $repetition, $accounts);
$balance->addBalanceLine($line); $balance->addBalanceLine($line);
} }
Log::debug('Create rest of the things.'); Log::debug('Create rest of the things.');
@@ -146,26 +143,23 @@ class BalanceReportHelper implements BalanceReportHelperInterface
/** /**
* @param Budget $budget * @param BudgetLimit $budgetLimit
* @param LimitRepetition $repetition * @param Collection $accounts
* @param Collection $accounts
* *
* @return BalanceLine * @return BalanceLine
*/ */
private function createBalanceLine(Budget $budget, LimitRepetition $repetition, Collection $accounts): BalanceLine private function createBalanceLine(BudgetLimit $budgetLimit, Collection $accounts): BalanceLine
{ {
$line = new BalanceLine; $line = new BalanceLine;
$budget->amount = $repetition->amount; $line->setBudget($budgetLimit->budget);
$line->setBudget($budget); $line->setBudgetLimit($budgetLimit);
$line->setStartDate($repetition->startdate);
$line->setEndDate($repetition->enddate);
// loop accounts: // loop accounts:
foreach ($accounts as $account) { foreach ($accounts as $account) {
$balanceEntry = new BalanceEntry; $balanceEntry = new BalanceEntry;
$balanceEntry->setAccount($account); $balanceEntry->setAccount($account);
$spent = $this->budgetRepository->spentInPeriod( $spent = $this->budgetRepository->spentInPeriod(
new Collection([$budget]), new Collection([$account]), $repetition->startdate, $repetition->enddate new Collection([$budgetLimit->budget]), new Collection([$account]), $budgetLimit->start_date, $budgetLimit->end_date
); );
$balanceEntry->setSpent($spent); $balanceEntry->setSpent($spent);
$line->addBalanceEntry($balanceEntry); $line->addBalanceEntry($balanceEntry);

View File

@@ -18,7 +18,7 @@ use Carbon\Carbon;
use FireflyIII\Helpers\Collection\Budget as BudgetCollection; use FireflyIII\Helpers\Collection\Budget as BudgetCollection;
use FireflyIII\Helpers\Collection\BudgetLine; use FireflyIII\Helpers\Collection\BudgetLine;
use FireflyIII\Models\Budget; use FireflyIII\Models\Budget;
use FireflyIII\Models\LimitRepetition; use FireflyIII\Models\BudgetLimit;
use FireflyIII\Repositories\Budget\BudgetRepositoryInterface; use FireflyIII\Repositories\Budget\BudgetRepositoryInterface;
use Illuminate\Support\Collection; use Illuminate\Support\Collection;
@@ -57,8 +57,8 @@ class BudgetReportHelper implements BudgetReportHelperInterface
/** @var Budget $budget */ /** @var Budget $budget */
foreach ($set as $budget) { foreach ($set as $budget) {
$repetitions = $budget->limitrepetitions()->before($end)->after($start)->get(); $budgetLimits = $this->repository->getBudgetLimits($budget, $start, $end);
if ($repetitions->count() == 0) { // no repetition(s) for this budget if ($budgetLimits->count() == 0) { // no budget limit(s) for this budget
$spent = $this->repository->spentInPeriod(new Collection([$budget]), $accounts, $start, $end);// spent for budget in time range $spent = $this->repository->spentInPeriod(new Collection([$budget]), $accounts, $start, $end);// spent for budget in time range
if ($spent > 0) { if ($spent > 0) {
$budgetLine = new BudgetLine; $budgetLine = new BudgetLine;
@@ -67,15 +67,15 @@ class BudgetReportHelper implements BudgetReportHelperInterface
} }
continue; continue;
} }
/** @var LimitRepetition $repetition */ /** @var BudgetLimit $budgetLimit */
foreach ($repetitions as $repetition) { // one or more repetitions for budget foreach ($budgetLimits as $budgetLimit) { // one or more repetitions for budget
$data = $this->calculateExpenses($budget, $repetition, $accounts); $data = $this->calculateExpenses($budget, $budgetLimit, $accounts);
$budgetLine = new BudgetLine; $budgetLine = new BudgetLine;
$budgetLine->setBudget($budget)->setRepetition($repetition) $budgetLine->setBudget($budget)->setBudgetLimit($budgetLimit)
->setLeft($data['left'])->setSpent($data['expenses'])->setOverspent($data['overspent']) ->setLeft($data['left'])->setSpent($data['expenses'])->setOverspent($data['overspent'])
->setBudgeted(strval($repetition->amount)); ->setBudgeted(strval($budgetLimit->amount));
$object->addBudgeted(strval($repetition->amount))->addSpent($data['spent']) $object->addBudgeted(strval($budgetLimit->amount))->addSpent($data['spent'])
->addLeft($data['left'])->addOverspent($data['overspent'])->addBudgetLine($budgetLine); ->addLeft($data['left'])->addOverspent($data['overspent'])->addBudgetLine($budgetLine);
} }
@@ -119,19 +119,19 @@ class BudgetReportHelper implements BudgetReportHelperInterface
} }
/** /**
* @param Budget $budget * @param Budget $budget
* @param LimitRepetition $repetition * @param BudgetLimit $budgetLimit
* @param Collection $accounts * @param Collection $accounts
* *
* @return array * @return array
*/ */
private function calculateExpenses(Budget $budget, LimitRepetition $repetition, Collection $accounts): array private function calculateExpenses(Budget $budget, BudgetLimit $budgetLimit, Collection $accounts): array
{ {
$array = []; $array = [];
$expenses = $this->repository->spentInPeriod(new Collection([$budget]), $accounts, $repetition->startdate, $repetition->enddate); $expenses = $this->repository->spentInPeriod(new Collection([$budget]), $accounts, $budgetLimit->start_date, $budgetLimit->end_date);
$array['left'] = bccomp(bcadd($repetition->amount, $expenses), '0') === 1 ? bcadd($repetition->amount, $expenses) : '0'; $array['left'] = bccomp(bcadd($budgetLimit->amount, $expenses), '0') === 1 ? bcadd($budgetLimit->amount, $expenses) : '0';
$array['spent'] = bccomp(bcadd($repetition->amount, $expenses), '0') === 1 ? $expenses : '0'; $array['spent'] = bccomp(bcadd($budgetLimit->amount, $expenses), '0') === 1 ? $expenses : '0';
$array['overspent'] = bccomp(bcadd($repetition->amount, $expenses), '0') === 1 ? '0' : bcadd($expenses, $repetition->amount); $array['overspent'] = bccomp(bcadd($budgetLimit->amount, $expenses), '0') === 1 ? '0' : bcadd($expenses, $budgetLimit->amount);
$array['expenses'] = $expenses; $array['expenses'] = $expenses;
return $array; return $array;

View File

@@ -285,8 +285,7 @@ class BudgetController extends Controller
*/ */
public function showByBudgetLimit(Request $request, Budget $budget, BudgetLimit $budgetLimit) public function showByBudgetLimit(Request $request, Budget $budget, BudgetLimit $budgetLimit)
{ {
if ($budgetLimit->budget->id if ($budgetLimit->budget->id != $budget->id) {
!= $budget->id) {
throw new FireflyException('This budget limit is not part of this budget.'); throw new FireflyException('This budget limit is not part of this budget.');
} }

View File

@@ -14,11 +14,12 @@ declare(strict_types = 1);
namespace FireflyIII\Http\Controllers\Chart; namespace FireflyIII\Http\Controllers\Chart;
use Carbon\Carbon; use Carbon\Carbon;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Generator\Chart\Basic\GeneratorInterface; use FireflyIII\Generator\Chart\Basic\GeneratorInterface;
use FireflyIII\Helpers\Collector\JournalCollectorInterface; use FireflyIII\Helpers\Collector\JournalCollectorInterface;
use FireflyIII\Http\Controllers\Controller; use FireflyIII\Http\Controllers\Controller;
use FireflyIII\Models\Budget; use FireflyIII\Models\Budget;
use FireflyIII\Models\LimitRepetition; use FireflyIII\Models\BudgetLimit;
use FireflyIII\Models\Transaction; use FireflyIII\Models\Transaction;
use FireflyIII\Models\TransactionType; use FireflyIII\Models\TransactionType;
use FireflyIII\Repositories\Budget\BudgetRepositoryInterface; use FireflyIII\Repositories\Budget\BudgetRepositoryInterface;
@@ -104,26 +105,30 @@ class BudgetController extends Controller
* @SuppressWarnings(PHPMD.CyclomaticComplexity) // it's exactly five. * @SuppressWarnings(PHPMD.CyclomaticComplexity) // it's exactly five.
* @param BudgetRepositoryInterface $repository * @param BudgetRepositoryInterface $repository
* @param Budget $budget * @param Budget $budget
* @param LimitRepetition $repetition * @param BudgetLimit $budgetLimit
* *
* @return \Symfony\Component\HttpFoundation\Response * @return \Symfony\Component\HttpFoundation\Response
*/ */
public function budgetLimit(BudgetRepositoryInterface $repository, Budget $budget, LimitRepetition $repetition) public function budgetLimit(BudgetRepositoryInterface $repository, Budget $budget, BudgetLimit $budgetLimit)
{ {
$start = clone $repetition->startdate; if ($budgetLimit->budget->id != $budget->id) {
$end = $repetition->enddate; throw new FireflyException('This budget limit is not part of this budget.');
}
$start = clone $budgetLimit->start_date;
$end = clone $budgetLimit->end_date;
$cache = new CacheProperties(); $cache = new CacheProperties();
$cache->addProperty($start); $cache->addProperty($start);
$cache->addProperty($end); $cache->addProperty($end);
$cache->addProperty('chart.budget.budget.limit'); $cache->addProperty('chart.budget.budget.limit');
$cache->addProperty($repetition->id); $cache->addProperty($budgetLimit->id);
if ($cache->has()) { if ($cache->has()) {
return Response::json($cache->get()); return Response::json($cache->get());
} }
$entries = []; $entries = [];
$amount = $repetition->amount; $amount = $budgetLimit->amount;
$budgetCollection = new Collection([$budget]); $budgetCollection = new Collection([$budget]);
while ($start <= $end) { while ($start <= $end) {
$spent = $repository->spentInPeriod($budgetCollection, new Collection, $start, $start); $spent = $repository->spentInPeriod($budgetCollection, new Collection, $start, $start);
@@ -160,9 +165,8 @@ class BudgetController extends Controller
if ($cache->has()) { if ($cache->has()) {
return Response::json($cache->get()); return Response::json($cache->get());
} }
$budgets = $repository->getActiveBudgets(); $budgets = $repository->getActiveBudgets();
$repetitions = $repository->getAllBudgetLimitRepetitions($start, $end); $chartData = [
$chartData = [
['label' => strval(trans('firefly.spent_in_budget')), 'entries' => [], 'type' => 'bar',], ['label' => strval(trans('firefly.spent_in_budget')), 'entries' => [], 'type' => 'bar',],
['label' => strval(trans('firefly.left_to_spend')), 'entries' => [], 'type' => 'bar',], ['label' => strval(trans('firefly.left_to_spend')), 'entries' => [], 'type' => 'bar',],
['label' => strval(trans('firefly.overspent')), 'entries' => [], 'type' => 'bar',], ['label' => strval(trans('firefly.overspent')), 'entries' => [], 'type' => 'bar',],
@@ -172,8 +176,8 @@ class BudgetController extends Controller
/** @var Budget $budget */ /** @var Budget $budget */
foreach ($budgets as $budget) { foreach ($budgets as $budget) {
// get relevant repetitions: // get relevant repetitions:
$filtered = $this->filterRepetitions($repetitions, $budget, $start, $end); $limits = $repository->getBudgetLimits($budget, $start, $end);
$expenses = $this->getExpensesForBudget($filtered, $budget, $start, $end); $expenses = $this->getExpensesForBudget($limits, $budget, $start, $end);
foreach ($expenses as $name => $row) { foreach ($expenses as $name => $row) {
$chartData[0]['entries'][$name] = $row['spent']; $chartData[0]['entries'][$name] = $row['spent'];
$chartData[1]['entries'][$name] = $row['left']; $chartData[1]['entries'][$name] = $row['left'];
@@ -224,25 +228,14 @@ class BudgetController extends Controller
$entries = $repository->getBudgetPeriodReport(new Collection([$budget]), $accounts, $start, $end); $entries = $repository->getBudgetPeriodReport(new Collection([$budget]), $accounts, $start, $end);
$key = Navigation::preferredCarbonFormat($start, $end); $key = Navigation::preferredCarbonFormat($start, $end);
$range = Navigation::preferredRangeFormat($start, $end); $range = Navigation::preferredRangeFormat($start, $end);
$current = clone $start;
// get the budget limits (if any)
$repetitions = $repository->getAllBudgetLimitRepetitions($start, $end);
$current = clone $start;
while ($current < $end) { while ($current < $end) {
$currentStart = Navigation::startOfPeriod($current, $range);
$currentEnd = Navigation::endOfPeriod($current, $range);
$reps = $repetitions->filter( $currentStart = Navigation::startOfPeriod($current, $range);
function (LimitRepetition $repetition) use ($budget, $currentStart, $currentEnd) { $currentEnd = Navigation::endOfPeriod($current, $range);
if ($repetition->budget_id === $budget->id && $repetition->startdate >= $currentStart && $repetition->enddate <= $currentEnd) { $budgetLimits = $repository->getBudgetLimits($budget, $currentStart, $currentEnd);
return true;
}
return false;
}
);
$index = $currentStart->format($key); $index = $currentStart->format($key);
$budgeted[$index] = $reps->sum('amount'); $budgeted[$index] = $budgetLimits->sum('amount');
$currentEnd->addDay(); $currentEnd->addDay();
$current = clone $currentEnd; $current = clone $currentEnd;
} }
@@ -312,46 +305,23 @@ class BudgetController extends Controller
return Response::json($data); return Response::json($data);
} }
/**
* @param Collection $repetitions
* @param Budget $budget
* @param Carbon $start
* @param Carbon $end
*
* @return Collection
*/
private function filterRepetitions(Collection $repetitions, Budget $budget, Carbon $start, Carbon $end): Collection
{
return $repetitions->filter(
function (LimitRepetition $repetition) use ($budget, $start, $end) {
if ($repetition->startdate < $end && $repetition->enddate > $start && $repetition->budget_id === $budget->id) {
return true;
}
return false;
}
);
}
/** /**
* *
* @SuppressWarnings(PHPMD.CyclomaticComplexity) // it's 6 but ok. * @SuppressWarnings(PHPMD.CyclomaticComplexity) // it's 6 but ok.
* *
* @param Collection $repetitions * @param Collection $limits
* @param Budget $budget * @param Budget $budget
* @param Carbon $start * @param Carbon $start
* @param Carbon $end * @param Carbon $end
* *
* @return array * @return array
*/ */
private function getExpensesForBudget(Collection $repetitions, Budget $budget, Carbon $start, Carbon $end): array private function getExpensesForBudget(Collection $limits, Budget $budget, Carbon $start, Carbon $end): array
{ {
/** @var BudgetRepositoryInterface $repository */ /** @var BudgetRepositoryInterface $repository */
$repository = app(BudgetRepositoryInterface::class); $repository = app(BudgetRepositoryInterface::class);
$return = []; $return = [];
if ($repetitions->count() === 0) { if ($limits->count() === 0) {
$spent = $repository->spentInPeriod(new Collection([$budget]), new Collection, $start, $end); $spent = $repository->spentInPeriod(new Collection([$budget]), new Collection, $start, $end);
if (bccomp($spent, '0') !== 0) { if (bccomp($spent, '0') !== 0) {
$return[$budget->name]['spent'] = bcmul($spent, '-1'); $return[$budget->name]['spent'] = bcmul($spent, '-1');
@@ -362,7 +332,7 @@ class BudgetController extends Controller
return $return; return $return;
} }
$rows = $this->spentInPeriodMulti($repository, $budget, $repetitions); $rows = $this->spentInPeriodMulti($repository, $budget, $limits);
foreach ($rows as $name => $row) { foreach ($rows as $name => $row) {
if (bccomp($row['spent'], '0') !== 0 || bccomp($row['left'], '0') !== 0) { if (bccomp($row['spent'], '0') !== 0 || bccomp($row['left'], '0') !== 0) {
$return[$name]['spent'] = bcmul($row['spent'], '-1'); $return[$name]['spent'] = bcmul($row['spent'], '-1');
@@ -386,26 +356,29 @@ class BudgetController extends Controller
* *
* @param BudgetRepositoryInterface $repository * @param BudgetRepositoryInterface $repository
* @param Budget $budget * @param Budget $budget
* @param Collection $repetitions * @param Collection $limits
* *
* @return array * @return array
*/ */
private function spentInPeriodMulti(BudgetRepositoryInterface $repository, Budget $budget, Collection $repetitions): array private function spentInPeriodMulti(BudgetRepositoryInterface $repository, Budget $budget, Collection $limits): array
{ {
$return = []; $return = [];
$format = strval(trans('config.month_and_day')); $format = strval(trans('config.month_and_day'));
$name = $budget->name; $name = $budget->name;
/** @var LimitRepetition $repetition */ /** @var BudgetLimit $budgetLimit */
foreach ($repetitions as $repetition) { foreach ($limits as $budgetLimit) {
$expenses = $repository->spentInPeriod(new Collection([$budget]), new Collection, $repetition->startdate, $repetition->enddate); $expenses = $repository->spentInPeriod(new Collection([$budget]), new Collection, $budgetLimit->start_date, $budgetLimit->end_date);
if ($repetitions->count() > 1) { if ($limits->count() > 1) {
$name = $budget->name . ' ' . trans( $name = $budget->name . ' ' . trans(
'firefly.between_dates', 'firefly.between_dates',
['start' => $repetition->startdate->formatLocalized($format), 'end' => $repetition->enddate->formatLocalized($format)] [
'start' => $budgetLimit->start_date->formatLocalized($format),
'end' => $budgetLimit->end_date->formatLocalized($format),
]
); );
} }
$amount = $repetition->amount; $amount = $budgetLimit->amount;
$left = bccomp(bcadd($amount, $expenses), '0') < 1 ? '0' : bcadd($amount, $expenses); $left = bccomp(bcadd($amount, $expenses), '0') < 1 ? '0' : bcadd($amount, $expenses);
$spent = $expenses; $spent = $expenses;
$overspent = bccomp(bcadd($amount, $expenses), '0') < 1 ? bcadd($amount, $expenses) : '0'; $overspent = bccomp(bcadd($amount, $expenses), '0') < 1 ? bcadd($amount, $expenses) : '0';

View File

@@ -20,14 +20,13 @@ use FireflyIII\Generator\Report\Category\MonthReportGenerator;
use FireflyIII\Helpers\Collector\JournalCollectorInterface; use FireflyIII\Helpers\Collector\JournalCollectorInterface;
use FireflyIII\Http\Controllers\Controller; use FireflyIII\Http\Controllers\Controller;
use FireflyIII\Models\Budget; use FireflyIII\Models\Budget;
use FireflyIII\Models\LimitRepetition; use FireflyIII\Models\BudgetLimit;
use FireflyIII\Models\Transaction; use FireflyIII\Models\Transaction;
use FireflyIII\Models\TransactionType; use FireflyIII\Models\TransactionType;
use FireflyIII\Repositories\Account\AccountRepositoryInterface; use FireflyIII\Repositories\Account\AccountRepositoryInterface;
use FireflyIII\Repositories\Budget\BudgetRepositoryInterface; use FireflyIII\Repositories\Budget\BudgetRepositoryInterface;
use FireflyIII\Support\CacheProperties; use FireflyIII\Support\CacheProperties;
use Illuminate\Support\Collection; use Illuminate\Support\Collection;
use Log;
use Navigation; use Navigation;
use Response; use Response;
@@ -206,7 +205,6 @@ class BudgetReportController extends Controller
$function = Navigation::preferredEndOfPeriod($start, $end); $function = Navigation::preferredEndOfPeriod($start, $end);
$chartData = []; $chartData = [];
$currentStart = clone $start; $currentStart = clone $start;
$limits = $repository->getAllBudgetLimitRepetitions($start, $end); // also for ALL budgets.
// prep chart data: // prep chart data:
foreach ($budgets as $budget) { foreach ($budgets as $budget) {
@@ -231,8 +229,9 @@ class BudgetReportController extends Controller
'entries' => [], 'entries' => [],
]; ];
} }
$sumOfExpenses = []; $allBudgetLimits = $repository->getAllBudgetLimits($start, $end);
$leftOfLimits = []; $sumOfExpenses = [];
$leftOfLimits = [];
while ($currentStart < $end) { while ($currentStart < $end) {
$currentEnd = clone $currentStart; $currentEnd = clone $currentStart;
$currentEnd = $currentEnd->$function(); $currentEnd = $currentEnd->$function();
@@ -241,20 +240,20 @@ class BudgetReportController extends Controller
/** @var Budget $budget */ /** @var Budget $budget */
foreach ($budgets as $budget) { foreach ($budgets as $budget) {
// get budget limit(s) for this period):
$budgetLimits = $this->filterBudgetLimits($allBudgetLimits, $budget, $currentStart, $currentEnd);
$currentExpenses = $expenses[$budget->id] ?? '0'; $currentExpenses = $expenses[$budget->id] ?? '0';
$sumOfExpenses[$budget->id] = $sumOfExpenses[$budget->id] ?? '0'; $sumOfExpenses[$budget->id] = $sumOfExpenses[$budget->id] ?? '0';
$sumOfExpenses[$budget->id] = bcadd($currentExpenses, $sumOfExpenses[$budget->id]); $sumOfExpenses[$budget->id] = bcadd($currentExpenses, $sumOfExpenses[$budget->id]);
$chartData[$budget->id]['entries'][$label] = round(bcmul($currentExpenses, '-1'), 2); $chartData[$budget->id]['entries'][$label] = round(bcmul($currentExpenses, '-1'), 2);
$chartData[$budget->id . '-sum']['entries'][$label] = round(bcmul($sumOfExpenses[$budget->id], '-1'), 2); $chartData[$budget->id . '-sum']['entries'][$label] = round(bcmul($sumOfExpenses[$budget->id], '-1'), 2);
$limit = $this->filterLimits($limits, $budget, $currentStart); if (count($budgetLimits) > 0) {
if (!is_null($limit->id)) { $budgetLimitId = $budgetLimits->first()->id;
$leftOfLimits[$limit->id] = $leftOfLimits[$limit->id] ?? strval($limit->amount); $leftOfLimits[$budgetLimitId] = $leftOfLimits[$budgetLimitId] ?? strval($budgetLimits->sum('amount'));
$leftOfLimits[$limit->id] = bcadd($leftOfLimits[$limit->id], $currentExpenses); $leftOfLimits[$budgetLimitId] = bcadd($leftOfLimits[$budgetLimitId], $currentExpenses);
$chartData[$budget->id . '-left']['entries'][$label] = round($leftOfLimits[$limit->id], 2); $chartData[$budget->id . '-left']['entries'][$label] = $leftOfLimits[$budgetLimitId];
} }
} }
$currentStart = clone $currentEnd; $currentStart = clone $currentEnd;
$currentStart->addDay(); $currentStart->addDay();
@@ -267,44 +266,32 @@ class BudgetReportController extends Controller
} }
/** /**
* @param $limits * Returns the budget limits belonging to the given budget and valid on the given day.
* @param $budget
* @param $currentStart
* *
* @return LimitRepetition * @param Collection $budgetLimits
* @param Carbon $start
* @param Carbon $end
*
* @return Collection
*/ */
private function filterLimits(Collection $limits, Budget $budget, Carbon $date): LimitRepetition private function filterBudgetLimits(Collection $budgetLimits, Budget $budget, Carbon $start, Carbon $end): Collection
{ {
Log::debug(sprintf('Start of filterLimits with %d limits.', $limits->count())); $set = $budgetLimits->filter(
$filtered = $limits->filter( function (BudgetLimit $budgetLimit) use ($budget, $start, $end) {
function (LimitRepetition $limit) use ($budget, $date) { if ($budgetLimit->budget_id === $budget->id
if ($limit->budget_id !== $budget->id) { && $budgetLimit->start_date->lte($start) // start of budget limit is on or before start
Log::debug(sprintf('LimitRepetition has budget #%d but expecting #%d', $limit->budget_id, $budget->id)); && $budgetLimit->end_date->gte($end) // end of budget limit is on or after end
) {
return false; return $budgetLimit;
}
if ($date < $limit->startdate || $date > $limit->enddate) {
Log::debug(
sprintf(
'Date %s is not between %s and %s',
$date->format('Y-m-d'), $limit->startdate->format('Y-m-d'), $limit->enddate->format('Y-m-d')
)
);
return false;
} }
return $limit; return false;
} }
); );
if ($filtered->count() === 1) {
return $filtered->first();
}
return new LimitRepetition; return $set;
} }
/** /**
* @param Collection $accounts * @param Collection $accounts
* @param Collection $budgets * @param Collection $budgets

View File

@@ -118,14 +118,6 @@ class Budget extends Model
return $value; return $value;
} }
/**
* @return \Illuminate\Database\Eloquent\Relations\HasManyThrough
*/
public function limitrepetitions()
{
return $this->hasManyThrough('FireflyIII\Models\LimitRepetition', 'FireflyIII\Models\BudgetLimit', 'budget_id');
}
/** /**
* @param $value * @param $value
*/ */

View File

@@ -67,14 +67,6 @@ class BudgetLimit extends Model
return $this->belongsTo('FireflyIII\Models\Budget'); return $this->belongsTo('FireflyIII\Models\Budget');
} }
/**
* @return \Illuminate\Database\Eloquent\Relations\HasMany
*/
public function limitrepetitions()
{
return $this->hasMany('FireflyIII\Models\LimitRepetition');
}
/** /**
* @param $value * @param $value
*/ */

View File

@@ -21,6 +21,7 @@ use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
/** /**
* Class LimitRepetition * Class LimitRepetition
* *
* @deprecated
* @package FireflyIII\Models * @package FireflyIII\Models
*/ */
class LimitRepetition extends Model class LimitRepetition extends Model

View File

@@ -18,7 +18,6 @@ use FireflyIII\Helpers\Collector\JournalCollectorInterface;
use FireflyIII\Models\AvailableBudget; use FireflyIII\Models\AvailableBudget;
use FireflyIII\Models\Budget; use FireflyIII\Models\Budget;
use FireflyIII\Models\BudgetLimit; use FireflyIII\Models\BudgetLimit;
use FireflyIII\Models\LimitRepetition;
use FireflyIII\Models\Transaction; use FireflyIII\Models\Transaction;
use FireflyIII\Models\TransactionCurrency; use FireflyIII\Models\TransactionCurrency;
use FireflyIII\Models\TransactionJournal; use FireflyIII\Models\TransactionJournal;
@@ -197,30 +196,35 @@ class BudgetRepository implements BudgetRepositoryInterface
* *
* @return Collection * @return Collection
*/ */
public function getAllBudgetLimitRepetitions(Carbon $start, Carbon $end): Collection public function getAllBudgetLimits(Carbon $start, Carbon $end): Collection
{ {
$query = LimitRepetition::leftJoin('budget_limits', 'limit_repetitions.budget_limit_id', '=', 'budget_limits.id') $set = BudgetLimit::leftJoin('budgets', 'budgets.id', '=', 'budget_limits.budget_id')
->leftJoin('budgets', 'budgets.id', '=', 'budget_limits.budget_id') ->with(['budget'])
->where( ->where('budgets.user_id', $this->user->id)
function (Builder $q1) use ($start, $end) { ->where(
$q1->where( function (Builder $q1) use ($start, $end) {
function (Builder $q2) use ($start, $end) { $q1->where(
$q2->where('limit_repetitions.enddate', '>=', $start->format('Y-m-d 00:00:00')); function (Builder $q2) use ($start, $end) {
$q2->where('limit_repetitions.enddate', '<=', $end->format('Y-m-d 00:00:00')); $q2->where('budget_limits.end_date', '>=', $start->format('Y-m-d 00:00:00'));
} $q2->where('budget_limits.end_date', '<=', $end->format('Y-m-d 00:00:00'));
) }
->orWhere( )
function (Builder $q3) use ($start, $end) { ->orWhere(
$q3->where('limit_repetitions.startdate', '>=', $start->format('Y-m-d 00:00:00')); function (Builder $q3) use ($start, $end) {
$q3->where('limit_repetitions.startdate', '<=', $end->format('Y-m-d 00:00:00')); $q3->where('budget_limits.start_date', '>=', $start->format('Y-m-d 00:00:00'));
} $q3->where('budget_limits.start_date', '<=', $end->format('Y-m-d 00:00:00'));
); }
} );
) }
->where('budgets.user_id', $this->user->id) )
->whereNull('budgets.deleted_at'); ->orWhere(
function (Builder $q4) use ($start, $end) {
$set = $query->get(['limit_repetitions.*', 'budget_limits.budget_id']); // or start is before start AND end is after end.
$q4->where('budget_limits.start_date', '<=', $start->format('Y-m-d 00:00:00'));
$q4->where('budget_limits.end_date', '>=', $end->format('Y-m-d 00:00:00'));
}
)
->get(['budget_limits.*']);
return $set; return $set;
} }
@@ -258,20 +262,30 @@ class BudgetRepository implements BudgetRepositoryInterface
$set = $budget->budgetLimits() $set = $budget->budgetLimits()
->where( ->where(
function (Builder $q1) use ($start, $end) { function (Builder $q1) use ($start, $end) {
// budget limit ends within period
$q1->where( $q1->where(
function (Builder $q2) use ($start, $end) { function (Builder $q2) use ($start, $end) {
$q2->where('budget_limits.end_date', '>=', $start->format('Y-m-d 00:00:00')); $q2->where('budget_limits.end_date', '>=', $start->format('Y-m-d 00:00:00'));
$q2->where('budget_limits.end_date', '<=', $end->format('Y-m-d 00:00:00')); $q2->where('budget_limits.end_date', '<=', $end->format('Y-m-d 00:00:00'));
} }
) )
// budget limit start within period
->orWhere( ->orWhere(
function (Builder $q3) use ($start, $end) { function (Builder $q3) use ($start, $end) {
$q3->where('budget_limits.start_date', '>=', $start->format('Y-m-d 00:00:00')); $q3->where('budget_limits.start_date', '>=', $start->format('Y-m-d 00:00:00'));
$q3->where('budget_limits.start_date', '<=', $end->format('Y-m-d 00:00:00')); $q3->where('budget_limits.start_date', '<=', $end->format('Y-m-d 00:00:00'));
} }
); );
} }
)->get(); )
->orWhere(
function (Builder $q4) use ($start, $end) {
// or start is before start AND end is after end.
$q4->where('budget_limits.start_date', '<=', $start->format('Y-m-d 00:00:00'));
$q4->where('budget_limits.end_date', '>=', $end->format('Y-m-d 00:00:00'));
}
)
->get();
return $set; return $set;
} }

View File

@@ -83,16 +83,6 @@ interface BudgetRepositoryInterface
*/ */
public function getActiveBudgets(): Collection; public function getActiveBudgets(): Collection;
/**
* @deprecated
*
* @param Carbon $start
* @param Carbon $end
*
* @return Collection
*/
public function getAllBudgetLimitRepetitions(Carbon $start, Carbon $end): Collection;
/** /**
* @param TransactionCurrency $currency * @param TransactionCurrency $currency
* @param Carbon $start * @param Carbon $start
@@ -102,6 +92,14 @@ interface BudgetRepositoryInterface
*/ */
public function getAvailableBudget(TransactionCurrency $currency, Carbon $start, Carbon $end): string; public function getAvailableBudget(TransactionCurrency $currency, Carbon $start, Carbon $end): string;
/**
* @param Carbon $start
* @param Carbon $end
*
* @return Collection
*/
public function getAllBudgetLimits(Carbon $start, Carbon $end): Collection;
/** /**
* @param Budget $budget * @param Budget $budget
* @param Carbon $start * @param Carbon $start

View File

@@ -75,7 +75,9 @@
<h3 class="box-title">{{ 'expense_per_budget'|_ }}</h3> <h3 class="box-title">{{ 'expense_per_budget'|_ }}</h3>
</div> </div>
<div class="box-body"> <div class="box-body">
<canvas id="budgets-out-pie-chart" style="margin:0 auto;" height="150" width="150"></canvas> <div style="width:75%;margin:0 auto;">
<canvas id="budgets-out-pie-chart" style="margin:0 auto;"></canvas>
</div>
<label style="font-weight:normal;"> <label style="font-weight:normal;">
<input type="checkbox" id="budgets-out-pie-chart-checked"> <input type="checkbox" id="budgets-out-pie-chart-checked">
<small>{{ 'include_not_in_budget'|_ }}</small> <small>{{ 'include_not_in_budget'|_ }}</small>
@@ -90,7 +92,9 @@
<h3 class="box-title">{{ 'expense_per_account'|_ }}</h3> <h3 class="box-title">{{ 'expense_per_account'|_ }}</h3>
</div> </div>
<div class="box-body"> <div class="box-body">
<canvas id="accounts-out-pie-chart" style="margin:0 auto;" height="150" width="150"></canvas> <div style="width:75%;margin:0 auto;">
<canvas id="accounts-out-pie-chart" style="margin:0 auto;"></canvas>
</div>
<label style="font-weight:normal;"> <label style="font-weight:normal;">
<input type="checkbox" id="accounts-out-pie-chart-checked"> <input type="checkbox" id="accounts-out-pie-chart-checked">
<small>{{ 'include_not_in_budget'|_ }}</small> <small>{{ 'include_not_in_budget'|_ }}</small>

View File

@@ -24,12 +24,12 @@
{% endif %} {% endif %}
{% if budgetLine.getRepetition.id %} {% if budgetLine.getBudgetLimit.id %}
<td class="hidden-xs" data-value="{{ budgetLine.getRepetition.startdate.format('Y-m-d') }}"> <td class="hidden-xs" data-value="{{ budgetLine.getBudgetLimit.start_date.format('Y-m-d') }}">
<a href="{{ route('budgets.show.repetition', [budgetLine.getBudget.id, budgetLine.getRepetition.id]) }}"> <a href="{{ route('budgets.show.limit', [budgetLine.getBudget.id, budgetLine.getBudgetLimit.id]) }}">
{{ budgetLine.getRepetition.startdate.formatLocalized(monthAndDayFormat) }} {{ budgetLine.getBudgetLimit.start_date.formatLocalized(monthAndDayFormat) }}
&mdash; &mdash;
{{ budgetLine.getRepetition.enddate.formatLocalized(monthAndDayFormat) }} {{ budgetLine.getBudgetLimit.end_date.formatLocalized(monthAndDayFormat) }}
</a> </a>
</td> </td>
{% else %} {% else %}
@@ -39,9 +39,9 @@
{% endif %} {% endif %}
{% if budgetLine.getRepetition.id %} {% if budgetLine.getBudgetLimit.id %}
<td data-value="{{ budgetLine.getRepetition.amount }}"> <td data-value="{{ budgetLine.getBudgetLimit.amount }}">
{{ budgetLine.getRepetition.amount|formatAmount }} {{ budgetLine.getBudgetLimit.amount|formatAmount }}
</td> </td>
{% else %} {% else %}
<td data-value="0"> <td data-value="0">