Files
firefly-iii/app/Helpers/Report/BalanceReportHelper.php

344 lines
11 KiB
PHP
Raw Normal View History

2016-01-27 21:35:59 +01:00
<?php
2016-02-05 12:08:25 +01:00
declare(strict_types = 1);
2016-01-27 21:35:59 +01:00
/**
* BalanceReportHelper.php
2016-04-01 16:44:46 +02:00
* Copyright (C) 2016 thegrumpydictator@gmail.com
2016-01-27 21:35:59 +01:00
*
* This software may be modified and distributed under the terms
* of the MIT license. See the LICENSE file for details.
*/
namespace FireflyIII\Helpers\Report;
2016-04-26 08:11:26 +02:00
use Auth;
2016-01-27 21:35:59 +01:00
use Carbon\Carbon;
2016-04-26 08:11:26 +02:00
use DB;
2016-01-27 21:35:59 +01:00
use FireflyIII\Helpers\Collection\Balance;
use FireflyIII\Helpers\Collection\BalanceEntry;
use FireflyIII\Helpers\Collection\BalanceHeader;
use FireflyIII\Helpers\Collection\BalanceLine;
2016-04-27 19:21:47 +02:00
use FireflyIII\Models\Account;
2016-01-27 21:52:21 +01:00
use FireflyIII\Models\Budget;
use FireflyIII\Models\Budget as BudgetModel;
2016-05-11 08:40:22 +02:00
use FireflyIII\Models\LimitRepetition;
2016-01-27 21:35:59 +01:00
use FireflyIII\Models\Tag;
use FireflyIII\Models\TransactionJournal;
2016-04-26 08:11:26 +02:00
use FireflyIII\Models\TransactionType;
2016-01-27 21:35:59 +01:00
use FireflyIII\Repositories\Budget\BudgetRepositoryInterface;
use FireflyIII\Repositories\Tag\TagRepositoryInterface;
2016-04-26 08:11:26 +02:00
use Illuminate\Database\Query\JoinClause;
2016-01-27 21:35:59 +01:00
use Illuminate\Support\Collection;
2016-05-11 08:40:22 +02:00
use Log;
2016-01-27 21:35:59 +01:00
/**
* Class BalanceReportHelper
*
* @package FireflyIII\Helpers\Report
*/
class BalanceReportHelper implements BalanceReportHelperInterface
{
/** @var BudgetRepositoryInterface */
protected $budgetRepository;
/** @var TagRepositoryInterface */
protected $tagRepository;
/**
* ReportHelper constructor.
*
*
* @param BudgetRepositoryInterface $budgetRepository
* @param TagRepositoryInterface $tagRepository
*/
2016-01-27 21:52:21 +01:00
public function __construct(BudgetRepositoryInterface $budgetRepository, TagRepositoryInterface $tagRepository)
2016-01-27 21:35:59 +01:00
{
$this->budgetRepository = $budgetRepository;
$this->tagRepository = $tagRepository;
}
/**
* @param Carbon $start
* @param Carbon $end
* @param Collection $accounts
*
* @return Balance
*/
2016-04-06 16:37:28 +02:00
public function getBalanceReport(Carbon $start, Carbon $end, Collection $accounts): Balance
2016-01-27 21:35:59 +01:00
{
$balance = new Balance;
2016-05-11 08:40:22 +02:00
Log::debug('Build new report.');
2016-01-27 21:35:59 +01:00
// build a balance header:
2016-05-11 08:40:22 +02:00
$header = new BalanceHeader;
$budgets = $this->budgetRepository->getBudgets();
$limitRepetitions = $this->budgetRepository->getAllBudgetLimitRepetitions($start, $end);
$spentData = $this->budgetRepository->journalsInPeriod($budgets, $accounts, $start, $end);
2016-01-27 21:35:59 +01:00
foreach ($accounts as $account) {
$header->addAccount($account);
2016-05-11 08:40:22 +02:00
Log::debug('Add account #' . $account->id . ' to header.');
2016-01-27 21:35:59 +01:00
}
2016-05-11 08:40:22 +02:00
/** @var LimitRepetition $repetition */
foreach ($limitRepetitions as $repetition) {
$budget = $this->budgetRepository->find($repetition->budget_id);
Log::debug('Create and add balance line for budget #' . $budget->id);
$balance->addBalanceLine($this->createBalanceLine($budget, $repetition, $accounts));
2016-01-27 21:35:59 +01:00
}
$balance->addBalanceLine($this->createEmptyBalanceLine($accounts, $spentData));
$balance->addBalanceLine($this->createTagsBalanceLine($accounts, $start, $end));
$balance->addBalanceLine($this->createDifferenceBalanceLine($accounts, $spentData, $start, $end));
$balance->setBalanceHeader($header);
2016-04-29 11:05:52 +02:00
// remove budgets without expenses from balance lines:
$balance = $this->removeUnusedBudgets($balance);
2016-01-27 21:35:59 +01:00
return $balance;
}
2016-04-26 08:09:10 +02:00
/**
* This method collects all transfers that are part of a "balancing act" tag
* and groups the amounts of those transfers by their destination account.
*
* This is used to indicate which expenses, usually outside of budgets, have been
* corrected by transfers from a savings account.
*
* @param Collection $accounts
* @param Carbon $start
* @param Carbon $end
*
* @return Collection
*/
private function allCoveredByBalancingActs(Collection $accounts, Carbon $start, Carbon $end): Collection
{
$ids = $accounts->pluck('id')->toArray();
2016-04-26 08:11:26 +02:00
$set = Auth::user()->tags()
2016-04-26 21:40:15 +02:00
->leftJoin('tag_transaction_journal', 'tag_transaction_journal.tag_id', '=', 'tags.id')
->leftJoin('transaction_journals', 'tag_transaction_journal.transaction_journal_id', '=', 'transaction_journals.id')
->leftJoin('transaction_types', 'transaction_journals.transaction_type_id', '=', 'transaction_types.id')
->leftJoin(
'transactions AS t_source', function (JoinClause $join) {
$join->on('transaction_journals.id', '=', 't_source.transaction_journal_id')->where('t_source.amount', '<', 0);
}
)
->leftJoin(
'transactions AS t_destination', function (JoinClause $join) {
$join->on('transaction_journals.id', '=', 't_destination.transaction_journal_id')->where('t_destination.amount', '>', 0);
}
)
->where('tags.tagMode', 'balancingAct')
->where('transaction_types.type', TransactionType::TRANSFER)
->where('transaction_journals.date', '>=', $start->format('Y-m-d'))
->where('transaction_journals.date', '<=', $end->format('Y-m-d'))
->whereNull('transaction_journals.deleted_at')
->whereIn('t_source.account_id', $ids)
->whereIn('t_destination.account_id', $ids)
->groupBy('t_destination.account_id')
->get(
[
't_destination.account_id',
DB::raw('SUM(`t_destination`.`amount`) as `sum`'),
]
);
2016-04-26 08:09:10 +02:00
return $set;
}
2016-01-27 21:35:59 +01:00
/**
2016-05-11 08:40:22 +02:00
* @param Budget $budget
* @param LimitRepetition $repetition
* @param Collection $accounts
2016-01-27 21:35:59 +01:00
*
* @return BalanceLine
*/
2016-05-11 08:40:22 +02:00
private function createBalanceLine(BudgetModel $budget, LimitRepetition $repetition, Collection $accounts): BalanceLine
2016-01-27 21:35:59 +01:00
{
2016-05-11 08:40:22 +02:00
Log::debug('Create line for budget #' . $budget->id . ' and repetition #' . $repetition->id);
2016-01-27 21:35:59 +01:00
$line = new BalanceLine;
2016-05-11 08:40:22 +02:00
$budget->amount = $repetition->amount;
2016-01-27 21:35:59 +01:00
$line->setBudget($budget);
2016-05-11 08:40:22 +02:00
$line->setStartDate($repetition->startdate);
$line->setEndDate($repetition->enddate);
2016-01-27 21:35:59 +01:00
// loop accounts:
foreach ($accounts as $account) {
$balanceEntry = new BalanceEntry;
$balanceEntry->setAccount($account);
2016-05-11 08:40:22 +02:00
$spent = $this->budgetRepository->spentInPeriod(
new Collection([$budget]), new Collection([$account]), $repetition->startdate, $repetition->enddate
2016-01-27 21:35:59 +01:00
);
$balanceEntry->setSpent($spent);
$line->addBalanceEntry($balanceEntry);
}
return $line;
}
2016-04-27 19:21:47 +02:00
/**
* @param Account $account
* @param Collection $spentData
* @param Collection $tagsLeft
*
* @return BalanceEntry
*/
private function createDifferenceBalanceEntry(Account $account, Collection $spentData, Collection $tagsLeft): BalanceEntry
{
$entry = $spentData->filter(
function (TransactionJournal $model) use ($account) {
return $model->account_id == $account->id && is_null($model->budget_id);
}
);
$spent = '0';
if (!is_null($entry->first())) {
$spent = $entry->first()->spent;
}
$leftEntry = $tagsLeft->filter(
function (Tag $tag) use ($account) {
return $tag->account_id == $account->id;
}
);
$left = '0';
if (!is_null($leftEntry->first())) {
$left = $leftEntry->first()->sum;
}
$diffValue = bcadd($spent, $left);
// difference:
$diffEntry = new BalanceEntry;
$diffEntry->setAccount($account);
$diffEntry->setSpent($diffValue);
return $diffEntry;
}
2016-01-27 21:35:59 +01:00
/**
* @param Collection $accounts
* @param Collection $spentData
* @param Carbon $start
* @param Carbon $end
*
2016-04-26 21:40:15 +02:00
*
2016-01-27 21:35:59 +01:00
*
* @return BalanceLine
*/
2016-04-06 16:37:28 +02:00
private function createDifferenceBalanceLine(Collection $accounts, Collection $spentData, Carbon $start, Carbon $end): BalanceLine
2016-01-27 21:35:59 +01:00
{
$diff = new BalanceLine;
2016-04-26 08:09:10 +02:00
$tagsLeft = $this->allCoveredByBalancingActs($accounts, $start, $end);
2016-01-27 21:35:59 +01:00
$diff->setRole(BalanceLine::ROLE_DIFFROLE);
2016-04-27 19:21:47 +02:00
/** @var Account $account */
2016-01-27 21:35:59 +01:00
foreach ($accounts as $account) {
2016-04-27 19:21:47 +02:00
$diffEntry = $this->createDifferenceBalanceEntry($account, $spentData, $tagsLeft);
2016-01-27 21:35:59 +01:00
$diff->addBalanceEntry($diffEntry);
}
return $diff;
}
/**
* @param Collection $accounts
* @param Collection $spentData
*
* @return BalanceLine
*/
2016-04-06 16:37:28 +02:00
private function createEmptyBalanceLine(Collection $accounts, Collection $spentData): BalanceLine
2016-01-27 21:35:59 +01:00
{
$empty = new BalanceLine;
foreach ($accounts as $account) {
$entry = $spentData->filter(
function (TransactionJournal $model) use ($account) {
return $model->account_id == $account->id && is_null($model->budget_id);
}
);
$spent = '0';
2016-01-27 21:35:59 +01:00
if (!is_null($entry->first())) {
$spent = $entry->first()->spent;
}
// budget
$budgetEntry = new BalanceEntry;
$budgetEntry->setAccount($account);
$budgetEntry->setSpent($spent);
$empty->addBalanceEntry($budgetEntry);
}
return $empty;
}
/**
* @param Collection $accounts
* @param Carbon $start
* @param Carbon $end
*
* @return BalanceLine
*/
2016-04-06 16:37:28 +02:00
private function createTagsBalanceLine(Collection $accounts, Carbon $start, Carbon $end): BalanceLine
2016-01-27 21:35:59 +01:00
{
$tags = new BalanceLine;
2016-04-26 08:09:10 +02:00
$tagsLeft = $this->allCoveredByBalancingActs($accounts, $start, $end);
2016-01-27 21:35:59 +01:00
$tags->setRole(BalanceLine::ROLE_TAGROLE);
foreach ($accounts as $account) {
$leftEntry = $tagsLeft->filter(
function (Tag $tag) use ($account) {
return $tag->account_id == $account->id;
}
);
$left = '0';
2016-01-27 21:35:59 +01:00
if (!is_null($leftEntry->first())) {
$left = $leftEntry->first()->sum;
}
// balanced by tags
$tagEntry = new BalanceEntry;
$tagEntry->setAccount($account);
$tagEntry->setLeft($left);
$tags->addBalanceEntry($tagEntry);
}
return $tags;
}
2016-04-29 11:05:52 +02:00
/**
* @param Balance $balance
*
* @return Balance
*/
private function removeUnusedBudgets(Balance $balance): Balance
{
$set = $balance->getBalanceLines();
$newSet = new Collection;
/** @var BalanceLine $entry */
foreach ($set as $entry) {
if (!is_null($entry->getBudget()->id)) {
$sum = '0';
/** @var BalanceEntry $balanceEntry */
foreach ($entry->getBalanceEntries() as $balanceEntry) {
$sum = bcadd($sum, $balanceEntry->getSpent());
}
if (bccomp($sum, '0') === -1) {
$newSet->push($entry);
}
continue;
}
$newSet->push($entry);
}
$balance->setBalanceLines($newSet);
return $balance;
}
}