. */ declare(strict_types=1); namespace FireflyIII\Helpers\Report; use Carbon\Carbon; use FireflyIII\Helpers\Collection\Balance; use FireflyIII\Helpers\Collection\BalanceEntry; use FireflyIII\Helpers\Collection\BalanceHeader; use FireflyIII\Helpers\Collection\BalanceLine; use FireflyIII\Helpers\Collector\GroupCollectorInterface; use FireflyIII\Models\Account; use FireflyIII\Models\Budget; use FireflyIII\Models\BudgetLimit; use FireflyIII\Models\TransactionType; use FireflyIII\Repositories\Budget\BudgetRepositoryInterface; use Illuminate\Support\Collection; use Log; /** * Class BalanceReportHelper. * * @codeCoverageIgnore */ class BalanceReportHelper implements BalanceReportHelperInterface { /** @var BudgetRepositoryInterface Budget repository */ protected $budgetRepository; /** * ReportHelper constructor. * * * @param BudgetRepositoryInterface $budgetRepository */ public function __construct(BudgetRepositoryInterface $budgetRepository) { $this->budgetRepository = $budgetRepository; if ('testing' === config('app.env')) { Log::warning(sprintf('%s should not be instantiated in the TEST environment!', get_class($this))); } } /** * Generate a balance report. * * @param Collection $accounts * @param Carbon $start * @param Carbon $end * * @return array */ public function getBalanceReport(Collection $accounts, Carbon $start, Carbon $end): array { Log::debug('Start of balance report'); $report = [ 'budgets' => [], 'accounts' => [], ]; /** @var Account $account */ foreach ($accounts as $account) { $report['accounts'][$account->id] = [ 'id' => $account->id, 'name' => $account->name, 'iban' => $account->iban, 'sum' => '0', ]; } $budgets = $this->budgetRepository->getBudgets(); // per budget, dan per balance line // of als het in een balance line valt dan daaronder en anders niet // kruistabel vullen? /** @var Budget $budget */ foreach ($budgets as $budget) { $budgetId = $budget->id; $report['budgets'][$budgetId] = [ 'budget_id' => $budgetId, 'budget_name' => $budget->name, 'spent' => [], // per account 'sums' => [], // per currency ]; $spent = []; /** @var GroupCollectorInterface $collector */ $collector = app(GroupCollectorInterface::class); $journals = $collector->setRange($start, $end)->setSourceAccounts($accounts)->setTypes([TransactionType::WITHDRAWAL])->setBudget($budget) ->getExtractedJournals(); /** @var array $journal */ foreach ($journals as $journal) { $sourceAccount = $journal['source_account_id']; $currencyId = $journal['currency_id']; $spent[$sourceAccount] = $spent[$sourceAccount] ?? [ 'source_account_id' => $sourceAccount, 'currency_id' => $journal['currency_id'], 'currency_code' => $journal['currency_code'], 'currency_name' => $journal['currency_name'], 'currency_symbol' => $journal['currency_symbol'], 'currency_decimal_places' => $journal['currency_decimal_places'], 'spent' => '0', ]; $spent[$sourceAccount]['spent'] = bcadd($spent[$sourceAccount]['spent'], $journal['amount']); // also fix sum: $report['sums'][$budgetId][$currencyId] = $report['sums'][$budgetId][$currencyId] ?? [ 'sum' => '0', 'currency_id' => $journal['currency_id'], 'currency_code' => $journal['currency_code'], 'currency_name' => $journal['currency_name'], 'currency_symbol' => $journal['currency_symbol'], 'currency_decimal_places' => $journal['currency_decimal_places'], ]; $report['sums'][$budgetId][$currencyId]['sum'] = bcadd($report['sums'][$budgetId][$currencyId]['sum'], $journal['amount']); $report['accounts'][$sourceAccount]['sum'] = bcadd($report['accounts'][$sourceAccount]['sum'], $journal['amount']); // add currency info for account sum $report['accounts'][$sourceAccount]['currency_id'] = $journal['currency_id']; $report['accounts'][$sourceAccount]['currency_code'] = $journal['currency_code']; $report['accounts'][$sourceAccount]['currency_name'] = $journal['currency_name']; $report['accounts'][$sourceAccount]['currency_symbol'] = $journal['currency_symbol']; $report['accounts'][$sourceAccount]['currency_decimal_places'] = $journal['currency_decimal_places']; } $report['budgets'][$budgetId]['spent'] = $spent; // get transactions in budget } return $report; // do sums: echo '
';
print_r($report);
exit;
$balance = new Balance;
$header = new BalanceHeader;
$budgetLimits = $this->budgetRepository->getAllBudgetLimits($start, $end);
foreach ($accounts as $account) {
Log::debug(sprintf('Add account %s to headers.', $account->name));
$header->addAccount($account);
}
/** @var BudgetLimit $budgetLimit */
foreach ($budgetLimits as $budgetLimit) {
if (null !== $budgetLimit->budget) {
$line = $this->createBalanceLine($budgetLimit, $accounts);
$balance->addBalanceLine($line);
}
}
$noBudgetLine = $this->createNoBudgetLine($accounts, $start, $end);
$balance->addBalanceLine($noBudgetLine);
$balance->setBalanceHeader($header);
Log::debug('Clear unused budgets.');
// remove budgets without expenses from balance lines:
$balance = $this->removeUnusedBudgets($balance);
Log::debug('Return report.');
return $balance;
}
/**
* Create one balance line.
*
* @param BudgetLimit $budgetLimit
* @param Collection $accounts
*
* @return BalanceLine
*/
private function createBalanceLine(BudgetLimit $budgetLimit, Collection $accounts): BalanceLine
{
$line = new BalanceLine;
$line->setBudget($budgetLimit->budget);
$line->setBudgetLimit($budgetLimit);
// loop accounts:
foreach ($accounts as $account) {
$balanceEntry = new BalanceEntry;
$balanceEntry->setAccount($account);
$spent = $this->budgetRepository->spentInPeriod(
new Collection([$budgetLimit->budget]),
new Collection([$account]),
$budgetLimit->start_date,
$budgetLimit->end_date
);
$balanceEntry->setSpent($spent);
$line->addBalanceEntry($balanceEntry);
}
return $line;
}
/**
* Create a line for transactions without a budget.
*
* @param Collection $accounts
* @param Carbon $start
* @param Carbon $end
*
* @return BalanceLine
*/
private function createNoBudgetLine(Collection $accounts, Carbon $start, Carbon $end): BalanceLine
{
$empty = new BalanceLine;
foreach ($accounts as $account) {
$spent = $this->budgetRepository->spentInPeriodWoBudget(new Collection([$account]), $start, $end);
// budget
$budgetEntry = new BalanceEntry;
$budgetEntry->setAccount($account);
$budgetEntry->setSpent($spent);
$empty->addBalanceEntry($budgetEntry);
}
return $empty;
}
/**
* Remove unused budgets from the report.
*
* @SuppressWarnings(PHPMD.CyclomaticComplexity)
* @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 (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;
}
}