firefly-iii/app/Repositories/Budget/OperationsRepository.php

300 lines
13 KiB
PHP
Raw Normal View History

2019-08-29 14:33:12 -05:00
<?php
/**
* OperationsRepository.php
2020-02-16 07:00:57 -06:00
* Copyright (c) 2019 james@firefly-iii.org
2019-08-29 14:33:12 -05:00
*
* This file is part of Firefly III (https://github.com/firefly-iii).
2019-08-29 14:33:12 -05:00
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
2019-08-29 14:33:12 -05:00
*
* This program is distributed in the hope that it will be useful,
2019-08-29 14:33:12 -05:00
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
2019-08-29 14:33:12 -05:00
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
2019-08-29 14:33:12 -05:00
*/
declare(strict_types=1);
namespace FireflyIII\Repositories\Budget;
use Carbon\Carbon;
use FireflyIII\Helpers\Collector\GroupCollectorInterface;
2022-03-28 05:24:16 -05:00
use FireflyIII\Models\Account;
2019-08-29 14:33:12 -05:00
use FireflyIII\Models\Budget;
2019-08-30 01:19:55 -05:00
use FireflyIII\Models\TransactionCurrency;
use FireflyIII\Models\TransactionType;
2022-03-28 05:24:16 -05:00
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
2019-08-29 14:33:12 -05:00
use FireflyIII\User;
2023-02-19 01:43:28 -06:00
use Illuminate\Contracts\Auth\Authenticatable;
use Illuminate\Support\Collection;
2019-08-29 14:33:12 -05:00
/**
* Class OperationsRepository
*/
class OperationsRepository implements OperationsRepositoryInterface
{
2020-09-18 09:14:17 -05:00
private User $user;
2019-08-29 14:33:12 -05:00
/**
* A method that returns the amount of money budgeted per day for this budget,
* on average.
*/
public function budgetedPerDay(Budget $budget): string
{
2023-10-29 00:33:43 -05:00
app('log')->debug(sprintf('Now with budget #%d "%s"', $budget->id, $budget->name));
2019-08-29 14:33:12 -05:00
$total = '0';
$count = 0;
foreach ($budget->budgetlimits as $limit) {
$diff = $limit->start_date->diffInDays($limit->end_date);
$diff = 0 === $diff ? 1 : $diff;
2023-11-05 12:41:37 -06:00
$amount = $limit->amount;
2022-12-29 12:42:26 -06:00
$perDay = bcdiv($amount, (string)$diff);
2019-08-29 14:33:12 -05:00
$total = bcadd($total, $perDay);
2023-12-20 12:35:52 -06:00
++$count;
2023-10-29 00:33:43 -05:00
app('log')->debug(sprintf('Found %d budget limits. Per day is %s, total is %s', $count, $perDay, $total));
2019-08-29 14:33:12 -05:00
}
$avg = $total;
2019-08-29 14:33:12 -05:00
if ($count > 0) {
2022-12-29 12:42:26 -06:00
$avg = bcdiv($total, (string)$count);
2019-08-29 14:33:12 -05:00
}
2023-10-29 00:33:43 -05:00
app('log')->debug(sprintf('%s / %d = %s = average.', $total, $count, $avg));
2019-08-29 14:33:12 -05:00
return $avg;
}
2019-08-30 01:12:15 -05:00
/**
* This method is being used to generate the budget overview in the year/multi-year report. Its used
* in both the year/multi-year budget overview AND in the accompanying chart.
*
* @deprecated
*/
public function getBudgetPeriodReport(Collection $budgets, Collection $accounts, Carbon $start, Carbon $end): array
{
$carbonFormat = app('navigation')->preferredCarbonFormat($start, $end);
$data = [];
2023-12-20 12:35:52 -06:00
2019-08-30 01:12:15 -05:00
// get all transactions:
/** @var GroupCollectorInterface $collector */
$collector = app(GroupCollectorInterface::class);
2019-08-30 01:12:15 -05:00
$collector->setAccounts($accounts)->setRange($start, $end);
$collector->setBudgets($budgets);
$journals = $collector->getExtractedJournals();
2019-08-30 01:12:15 -05:00
// loop transactions:
/** @var array $journal */
foreach ($journals as $journal) {
// prep data array for currency:
$budgetId = (int)$journal['budget_id'];
$budgetName = $journal['budget_name'];
$currencyId = (int)$journal['currency_id'];
$key = sprintf('%d-%d', $budgetId, $currencyId);
2019-08-30 01:12:15 -05:00
$data[$key] ??= [
2022-12-29 12:42:26 -06:00
'id' => $budgetId,
'name' => sprintf('%s (%s)', $budgetName, $journal['currency_name']),
'sum' => '0',
'currency_id' => $currencyId,
'currency_code' => $journal['currency_code'],
'currency_name' => $journal['currency_name'],
'currency_symbol' => $journal['currency_symbol'],
'currency_decimal_places' => $journal['currency_decimal_places'],
'entries' => [],
];
2019-08-30 01:12:15 -05:00
$date = $journal['date']->format($carbonFormat);
2022-12-30 13:25:04 -06:00
$data[$key]['entries'][$date] = bcadd($data[$key]['entries'][$date] ?? '0', $journal['amount']);
2019-08-30 01:12:15 -05:00
}
return $data;
}
/**
* This method returns a list of all the withdrawal transaction journals (as arrays) set in that period
* which have the specified budget set to them. It's grouped per currency, with as few details in the array
* as possible. Amounts are always negative.
*/
public function listExpenses(Carbon $start, Carbon $end, ?Collection $accounts = null, ?Collection $budgets = null): array
{
/** @var GroupCollectorInterface $collector */
$collector = app(GroupCollectorInterface::class);
$collector->setUser($this->user)->setRange($start, $end)->setTypes([TransactionType::WITHDRAWAL]);
if (null !== $accounts && $accounts->count() > 0) {
$collector->setAccounts($accounts);
}
if (null !== $budgets && $budgets->count() > 0) {
$collector->setBudgets($budgets);
}
2023-11-05 09:55:16 -06:00
if (null === $budgets || 0 === $budgets->count()) {
$collector->setBudgets($this->getBudgets());
}
$collector->withBudgetInformation()->withAccountInformation()->withCategoryInformation();
$journals = $collector->getExtractedJournals();
$array = [];
foreach ($journals as $journal) {
$currencyId = (int)$journal['currency_id'];
$budgetId = (int)$journal['budget_id'];
$budgetName = (string)$journal['budget_name'];
// catch "no category" entries.
if (0 === $budgetId) {
continue;
}
// info about the currency:
$array[$currencyId] ??= [
2022-12-29 12:42:26 -06:00
'budgets' => [],
'currency_id' => $currencyId,
'currency_name' => $journal['currency_name'],
'currency_symbol' => $journal['currency_symbol'],
'currency_code' => $journal['currency_code'],
'currency_decimal_places' => $journal['currency_decimal_places'],
];
// info about the categories:
2023-12-09 23:45:59 -06:00
$array[$currencyId]['budgets'][$budgetId] ??= [
2022-12-29 12:42:26 -06:00
'id' => $budgetId,
'name' => $budgetName,
'transaction_journals' => [],
];
// add journal to array:
// only a subset of the fields.
2022-12-29 12:42:26 -06:00
$journalId = (int)$journal['transaction_journal_id'];
$array[$currencyId]['budgets'][$budgetId]['transaction_journals'][$journalId] = [
'amount' => app('steam')->negative($journal['amount']),
'destination_account_id' => $journal['destination_account_id'],
'destination_account_name' => $journal['destination_account_name'],
'source_account_id' => $journal['source_account_id'],
'source_account_name' => $journal['source_account_name'],
'category_name' => $journal['category_name'],
'description' => $journal['description'],
'transaction_group_id' => $journal['transaction_group_id'],
'date' => $journal['date'],
];
}
return $array;
}
2023-12-20 12:35:52 -06:00
public function setUser(null|Authenticatable|User $user): void
2022-12-29 12:42:26 -06:00
{
2023-10-30 13:49:40 -05:00
if ($user instanceof User) {
2023-02-19 01:43:28 -06:00
$this->user = $user;
}
2022-12-29 12:42:26 -06:00
}
private function getBudgets(): Collection
{
/** @var BudgetRepositoryInterface $repos */
$repos = app(BudgetRepositoryInterface::class);
return $repos->getActiveBudgets();
}
2019-08-30 01:19:55 -05:00
/**
2023-11-30 22:15:59 -06:00
* @SuppressWarnings(PHPMD.ExcessiveParameterList)
*/
2022-10-30 08:24:28 -05:00
public function sumExpenses(
2023-06-21 05:34:58 -05:00
Carbon $start,
Carbon $end,
?Collection $accounts = null,
?Collection $budgets = null,
2022-10-30 08:24:28 -05:00
?TransactionCurrency $currency = null
2023-12-20 12:35:52 -06:00
): array {
// app('log')->debug(sprintf('Now in %s', __METHOD__));
2020-11-08 07:06:49 -06:00
$start->startOfDay();
$end->endOfDay();
2022-03-28 05:24:16 -05:00
// this collector excludes all transfers TO
// liabilities (which are also withdrawals)
// because those expenses only become expenses
// once they move from the liability to the friend.
// TODO this filter must be somewhere in AccountRepositoryInterface because I suspect its needed more often (A113)
$repository = app(AccountRepositoryInterface::class);
$repository->setUser($this->user);
$subset = $repository->getAccountsByType(config('firefly.valid_liabilities'));
$selection = new Collection();
2023-12-20 12:35:52 -06:00
2022-03-28 05:24:16 -05:00
/** @var Account $account */
2022-03-29 09:42:10 -05:00
foreach ($subset as $account) {
2022-03-28 05:24:16 -05:00
if ('credit' === $repository->getMetaValue($account, 'liability_direction')) {
$selection->push($account);
}
}
/** @var GroupCollectorInterface $collector */
$collector = app(GroupCollectorInterface::class);
2022-03-28 05:24:16 -05:00
$collector->setUser($this->user)
2023-12-20 12:35:52 -06:00
->setRange($start, $end)
->excludeDestinationAccounts($selection)
->setTypes([TransactionType::WITHDRAWAL])
;
2020-10-01 05:48:27 -05:00
if (null !== $accounts) {
$collector->setAccounts($accounts);
}
2020-10-01 05:48:27 -05:00
if (null === $budgets) {
$budgets = $this->getBudgets();
}
if (null !== $currency) {
$collector->setCurrency($currency);
}
$collector->setBudgets($budgets);
$journals = $collector->getExtractedJournals();
2020-10-01 05:48:27 -05:00
// same but for foreign currencies:
if (null !== $currency) {
2023-12-20 12:35:52 -06:00
// app('log')->debug(sprintf('Currency is "%s".', $currency->name));
2020-10-01 05:48:27 -05:00
/** @var GroupCollectorInterface $collector */
$collector = app(GroupCollectorInterface::class);
$collector->setUser($this->user)->setRange($start, $end)->setTypes([TransactionType::WITHDRAWAL])
2023-12-20 12:35:52 -06:00
->setForeignCurrency($currency)->setBudgets($budgets)
;
2020-10-01 05:48:27 -05:00
if (null !== $accounts) {
$collector->setAccounts($accounts);
}
$result = $collector->getExtractedJournals();
2023-12-20 12:35:52 -06:00
// app('log')->debug(sprintf('Found %d journals with currency %s.', count($result), $currency->code));
2020-10-01 05:48:27 -05:00
// do not use array_merge because you want keys to overwrite (otherwise you get double results):
$journals = $result + $journals;
2020-10-01 05:48:27 -05:00
}
$array = [];
foreach ($journals as $journal) {
2022-12-29 12:42:26 -06:00
$currencyId = (int)$journal['currency_id'];
$array[$currencyId] ??= [
2022-12-29 12:42:26 -06:00
'sum' => '0',
'currency_id' => $currencyId,
'currency_name' => $journal['currency_name'],
'currency_symbol' => $journal['currency_symbol'],
'currency_code' => $journal['currency_code'],
'currency_decimal_places' => $journal['currency_decimal_places'],
];
$array[$currencyId]['sum'] = bcadd($array[$currencyId]['sum'], app('steam')->negative($journal['amount']));
2020-10-01 05:48:27 -05:00
// also do foreign amount:
$foreignId = (int)$journal['foreign_currency_id'];
2021-03-11 23:20:01 -06:00
if (0 !== $foreignId) {
$array[$foreignId] ??= [
2022-12-29 12:42:26 -06:00
'sum' => '0',
'currency_id' => $foreignId,
'currency_name' => $journal['foreign_currency_name'],
'currency_symbol' => $journal['foreign_currency_symbol'],
'currency_code' => $journal['foreign_currency_code'],
'currency_decimal_places' => $journal['foreign_currency_decimal_places'],
];
2020-10-01 05:48:27 -05:00
$array[$foreignId]['sum'] = bcadd($array[$foreignId]['sum'], app('steam')->negative($journal['foreign_amount']));
}
}
2021-03-11 23:20:01 -06:00
2021-08-20 13:49:42 -05:00
return $array;
2021-03-11 23:20:01 -06:00
}
}