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

946 lines
33 KiB
PHP
Raw Normal View History

2015-02-22 02:46:21 -06:00
<?php
/**
* BudgetRepository.php
2017-10-21 01:40:00 -05:00
* Copyright (c) 2017 thegrumpydictator@gmail.com
*
2017-10-21 01:40:00 -05:00
* This file is part of Firefly III.
*
2017-10-21 01:40:00 -05:00
* Firefly III is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Firefly III is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
2017-12-17 07:44:05 -06:00
* along with Firefly III. If not, see <http://www.gnu.org/licenses/>.
*/
declare(strict_types=1);
2015-02-22 02:46:21 -06:00
namespace FireflyIII\Repositories\Budget;
use Carbon\Carbon;
2018-06-24 01:33:06 -05:00
use Exception;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Helpers\Collector\JournalCollectorInterface;
2017-09-27 01:45:27 -05:00
use FireflyIII\Models\AccountType;
2016-12-22 09:36:56 -06:00
use FireflyIII\Models\AvailableBudget;
2015-02-22 02:46:21 -06:00
use FireflyIII\Models\Budget;
use FireflyIII\Models\BudgetLimit;
2016-10-29 09:11:54 -05:00
use FireflyIII\Models\Transaction;
2016-12-22 09:36:56 -06:00
use FireflyIII\Models\TransactionCurrency;
2016-03-02 06:55:48 -06:00
use FireflyIII\Models\TransactionJournal;
use FireflyIII\Models\TransactionType;
2017-09-27 01:45:27 -05:00
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
use FireflyIII\User;
use Illuminate\Database\Eloquent\Builder;
2015-04-05 03:36:28 -05:00
use Illuminate\Support\Collection;
2017-11-24 10:05:22 -06:00
use Log;
2016-11-26 01:41:15 -06:00
use Navigation;
2016-11-19 06:37:44 -06:00
use stdClass;
2015-02-22 02:46:21 -06:00
/**
2017-11-15 05:25:49 -06:00
* Class BudgetRepository.
2015-02-22 02:46:21 -06:00
*/
2016-05-05 23:15:46 -05:00
class BudgetRepository implements BudgetRepositoryInterface
2015-02-22 02:46:21 -06:00
{
/** @var User */
private $user;
2018-03-24 00:46:37 -05:00
/**
* A method that returns the amount of money budgeted per day for this budget,
* on average.
*
* @param Budget $budget
*
* @return string
*/
public function budgetedPerDay(Budget $budget): string
{
Log::debug(sprintf('Now with budget #%d "%s"', $budget->id, $budget->name));
2018-03-24 00:46:37 -05:00
$total = '0';
$count = 0;
foreach ($budget->budgetlimits as $limit) {
$diff = $limit->start_date->diffInDays($limit->end_date);
$diff = 0 === $diff ? 1 : $diff;
2018-04-02 08:10:40 -05:00
$amount = (string)$limit->amount;
$perDay = bcdiv($amount, (string)$diff);
2018-03-24 00:46:37 -05:00
$total = bcadd($total, $perDay);
$count++;
Log::debug(sprintf('Found %d budget limits. Per day is %s, total is %s', $count, $perDay, $total));
2018-03-24 00:46:37 -05:00
}
$avg = $total;
if ($count > 0) {
2018-04-02 08:10:40 -05:00
$avg = bcdiv($total, (string)$count);
2018-03-24 00:46:37 -05:00
}
Log::debug(sprintf('%s / %d = %s = average.', $total, $count, $avg));
2018-03-24 00:46:37 -05:00
return $avg;
}
2016-06-16 13:52:59 -05:00
/**
* @return bool
*/
public function cleanupBudgets(): bool
{
// delete limits with amount 0:
BudgetLimit::where('amount', 0)->delete();
2018-02-19 13:32:33 -06:00
// do the clean up by hand because Sqlite can be tricky with this.
$budgetLimits = BudgetLimit::orderBy('created_at', 'DESC')->get(['id', 'budget_id', 'start_date', 'end_date']);
$count = [];
/** @var BudgetLimit $budgetLimit */
foreach ($budgetLimits as $budgetLimit) {
$key = $budgetLimit->budget_id . '-' . $budgetLimit->start_date->format('Y-m-d') . $budgetLimit->end_date->format('Y-m-d');
if (isset($count[$key])) {
// delete it!
BudgetLimit::find($budgetLimit->id)->delete();
2017-10-19 11:35:19 -05:00
}
2018-02-19 13:32:33 -06:00
$count[$key] = true;
2017-10-19 11:35:19 -05:00
}
2016-06-16 13:52:59 -05:00
return true;
}
2017-10-05 04:49:06 -05:00
/**
* This method collects various info on budgets, used on the budget page and on the index.
*
* @param Collection $budgets
* @param Carbon $start
* @param Carbon $end
*
* @return array
* @SuppressWarnings(PHPMD.CyclomaticComplexity)
*/
public function collectBudgetInformation(Collection $budgets, Carbon $start, Carbon $end): array
{
// get account information
/** @var AccountRepositoryInterface $accountRepository */
$accountRepository = app(AccountRepositoryInterface::class);
2017-11-12 01:42:25 -06:00
$accounts = $accountRepository->getAccountsByType([AccountType::DEFAULT, AccountType::ASSET]);
2017-11-18 09:30:45 -06:00
$defaultCurrency = app('amount')->getDefaultCurrency();
2017-10-05 04:49:06 -05:00
$return = [];
/** @var Budget $budget */
foreach ($budgets as $budget) {
$budgetId = $budget->id;
$return[$budgetId] = [
'spent' => $this->spentInPeriod(new Collection([$budget]), $accounts, $start, $end),
'budgeted' => '0',
'currentRep' => false,
];
$budgetLimits = $this->getBudgetLimits($budget, $start, $end);
$otherLimits = new Collection;
// get all the budget limits relevant between start and end and examine them:
/** @var BudgetLimit $limit */
foreach ($budgetLimits as $limit) {
if ($limit->start_date->isSameDay($start) && $limit->end_date->isSameDay($end)
) {
$return[$budgetId]['currentLimit'] = $limit;
2017-11-18 04:32:35 -06:00
$return[$budgetId]['budgeted'] = round($limit->amount, $defaultCurrency->decimal_places);
2017-10-05 04:49:06 -05:00
continue;
}
// otherwise it's just one of the many relevant repetitions:
$otherLimits->push($limit);
}
$return[$budgetId]['otherLimits'] = $otherLimits;
}
return $return;
}
2018-06-24 06:20:29 -05:00
/**
* Deletes a budget limit.
*
* @param BudgetLimit $budgetLimit
*/
public function deleteBudgetLimit(BudgetLimit $budgetLimit): void
{
try {
$budgetLimit->delete();
} catch (Exception $e) {
Log::error(sprintf('Could not delete budget limit: %s', $e->getMessage()));
}
}
2015-02-22 08:40:13 -06:00
/**
* @param Budget $budget
*
2016-04-05 15:00:03 -05:00
* @return bool
2015-02-22 08:40:13 -06:00
*/
2016-04-05 15:00:03 -05:00
public function destroy(Budget $budget): bool
2015-02-22 08:40:13 -06:00
{
2018-06-24 01:33:06 -05:00
try {
$budget->delete();
} catch (Exception $e) {
Log::error(sprintf('Could not delete budget: %s', $e->getMessage()));
}
2015-02-22 08:40:13 -06:00
return true;
}
2018-06-24 01:33:06 -05:00
/**
* @param AvailableBudget $availableBudget
*/
public function destroyAvailableBudget(AvailableBudget $availableBudget): void
{
try {
$availableBudget->delete();
} catch (Exception $e) {
Log::error(sprintf('Could not delete available budget: %s', $e->getMessage()));
}
}
/**
* Destroy a budget limit.
*
* @param BudgetLimit $budgetLimit
*/
public function destroyBudgetLimit(BudgetLimit $budgetLimit): void
{
try {
$budgetLimit->delete();
} catch (Exception $e) {
Log::info(sprintf('Could not delete budget limit: %s', $e->getMessage()));
}
}
2016-11-19 06:37:44 -06:00
/**
2017-11-15 05:25:49 -06:00
* Filters entries from the result set generated by getBudgetPeriodReport.
2016-11-19 06:37:44 -06:00
*
* @param Collection $set
* @param int $budgetId
* @param array $periods
*
* @return array
*/
public function filterAmounts(Collection $set, int $budgetId, array $periods): array
{
$arr = [];
$keys = array_keys($periods);
foreach ($keys as $period) {
/** @var stdClass $object */
$result = $set->filter(
function (TransactionJournal $object) use ($budgetId, $period) {
2018-04-02 08:10:40 -05:00
$result = (string)$object->period_marker === (string)$period && $budgetId === (int)$object->budget_id;
2016-11-19 06:37:44 -06:00
return $result;
}
);
$amount = '0';
2017-11-15 05:25:49 -06:00
if (null !== $result->first()) {
2016-11-19 06:37:44 -06:00
$amount = $result->first()->sum_of_period;
}
$arr[$period] = $amount;
}
return $arr;
}
2016-04-01 06:17:07 -05:00
/**
* Find a budget.
*
* @param int $budgetId
*
* @return Budget
*/
public function find(int $budgetId): Budget
{
$budget = $this->user->budgets()->find($budgetId);
2017-11-15 05:25:49 -06:00
if (null === $budget) {
2016-04-01 06:17:07 -05:00
$budget = new Budget;
}
return $budget;
}
/**
* Find a budget.
*
* @param string $name
*
2018-02-16 08:19:19 -06:00
* @return Budget|null
*/
2018-02-16 08:19:19 -06:00
public function findByName(string $name): ?Budget
{
$budgets = $this->user->budgets()->get(['budgets.*']);
/** @var Budget $budget */
foreach ($budgets as $budget) {
if ($budget->name === $name) {
return $budget;
}
}
2018-02-16 08:19:19 -06:00
return null;
}
/**
* Find a budget or return NULL
*
2018-07-05 11:02:02 -05:00
* @param int $budgetId |null
2018-02-16 08:19:19 -06:00
*
* @return Budget|null
*/
2018-07-05 11:02:02 -05:00
public function findNull(int $budgetId = null): ?Budget
2018-02-16 08:19:19 -06:00
{
2018-07-05 11:02:02 -05:00
if (null === $budgetId) {
return null;
}
2018-02-16 08:19:19 -06:00
return $this->user->budgets()->find($budgetId);
}
/**
* This method returns the oldest journal or transaction date known to this budget.
* Will cache result.
*
* @param Budget $budget
*
* @return Carbon
*/
public function firstUseDate(Budget $budget): Carbon
{
$oldest = Carbon::create()->startOfYear();
$journal = $budget->transactionJournals()->orderBy('date', 'ASC')->first();
2017-11-15 05:25:49 -06:00
if (null !== $journal) {
$oldest = $journal->date < $oldest ? $journal->date : $oldest;
}
$transaction = $budget
->transactions()
->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.id')
->orderBy('transaction_journals.date', 'ASC')->first(['transactions.*', 'transaction_journals.date']);
2017-11-15 05:25:49 -06:00
if (null !== $transaction) {
2016-05-11 10:17:43 -05:00
$carbon = new Carbon($transaction->date);
$oldest = $carbon < $oldest ? $carbon : $oldest;
}
return $oldest;
}
2015-12-27 14:17:04 -06:00
/**
* @return Collection
*/
2016-04-05 15:00:03 -05:00
public function getActiveBudgets(): Collection
2015-12-27 14:17:04 -06:00
{
/** @var Collection $set */
$set = $this->user->budgets()->where('active', 1)->get();
2015-12-27 14:17:04 -06:00
$set = $set->sortBy(
function (Budget $budget) {
return strtolower($budget->name);
}
);
return $set;
}
/**
* @param Carbon $start
* @param Carbon $end
*
* @return Collection
*/
2018-06-24 06:20:29 -05:00
public function getAllBudgetLimits(Carbon $start = null, Carbon $end = null): Collection
{
2018-06-24 06:20:29 -05:00
// both are NULL:
if (null === $start && null === $end) {
$set = BudgetLimit::leftJoin('budgets', 'budgets.id', '=', 'budget_limits.budget_id')
->with(['budget'])
->where('budgets.user_id', $this->user->id)
->get(['budget_limits.*']);
return $set;
}
// one of the two is NULL.
if (null === $start xor null === $end) {
$query = BudgetLimit::leftJoin('budgets', 'budgets.id', '=', 'budget_limits.budget_id')
->with(['budget'])
->where('budgets.user_id', $this->user->id);
if (null !== $end) {
// end date must be before $end.
$query->where('end_date', '<=', $end->format('Y-m-d 00:00:00'));
}
if (null !== $start) {
// start date must be after $start.
$query->where('start_date', '>=', $start->format('Y-m-d 00:00:00'));
}
$set = $query->get(['budget_limits.*']);
return $set;
}
// neither are NULL:
2016-12-30 01:41:48 -06:00
$set = BudgetLimit::leftJoin('budgets', 'budgets.id', '=', 'budget_limits.budget_id')
->with(['budget'])
->where('budgets.user_id', $this->user->id)
->where(
function (Builder $q5) use ($start, $end) {
$q5->where(
function (Builder $q1) use ($start, $end) {
$q1->where(
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', '<=', $end->format('Y-m-d 00:00:00'));
}
)
->orWhere(
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', '<=', $end->format('Y-m-d 00:00:00'));
}
);
2016-12-30 01:41:48 -06:00
}
)
->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'));
2016-12-30 01:41:48 -06:00
}
);
}
)->get(['budget_limits.*']);
2015-07-09 04:13:38 -05:00
return $set;
2015-04-05 03:36:28 -05:00
}
2016-12-22 09:36:56 -06:00
/**
* @param TransactionCurrency $currency
* @param Carbon $start
* @param Carbon $end
*
* @return string
*/
public function getAvailableBudget(TransactionCurrency $currency, Carbon $start, Carbon $end): string
{
$amount = '0';
$availableBudget = $this->user->availableBudgets()
->where('transaction_currency_id', $currency->id)
2018-03-07 13:21:51 -06:00
->where('start_date', $start->format('Y-m-d 00:00:00'))
->where('end_date', $end->format('Y-m-d 00:00:00'))->first();
2017-11-15 05:25:49 -06:00
if (null !== $availableBudget) {
2018-04-02 08:10:40 -05:00
$amount = (string)$availableBudget->amount;
2016-12-22 09:36:56 -06:00
}
return $amount;
}
2018-06-24 01:33:06 -05:00
/**
* Returns all available budget objects.
*
* @return Collection
*/
public function getAvailableBudgets(): Collection
{
return $this->user->availableBudgets()->get();
}
/**
* Calculate the average amount in the budgets available in this period.
* Grouped by day.
*
* @param Carbon $start
* @param Carbon $end
*
* @return string
*/
public function getAverageAvailable(Carbon $start, Carbon $end): string
{
/** @var Collection $list */
$list = $this->user->availableBudgets()
->where('start_date', '>=', $start->format('Y-m-d 00:00:00'))
->where('end_date', '<=', $end->format('Y-m-d 00:00:00'))
->get();
if (0 === $list->count()) {
return '0';
}
$total = '0';
$days = 0;
/** @var AvailableBudget $availableBudget */
foreach ($list as $availableBudget) {
$total = bcadd($availableBudget->amount, $total);
$days += $availableBudget->start_date->diffInDays($availableBudget->end_date);
}
$avg = bcdiv($total, (string)$days);
return $avg;
}
2016-12-29 13:48:33 -06:00
/**
* @param Budget $budget
* @param Carbon $start
* @param Carbon $end
*
* @return Collection
*/
2018-06-24 06:20:29 -05:00
public function getBudgetLimits(Budget $budget, Carbon $start = null, Carbon $end = null): Collection
2016-12-29 13:48:33 -06:00
{
2018-06-24 06:20:29 -05:00
if (null === $end && null === $start) {
return $budget->budgetlimits()->orderBy('budget_limits.start_date', 'DESC')->get(['budget_limits.*']);
}
if (null === $end xor null === $start) {
$query = $budget->budgetlimits()->orderBy('budget_limits.start_date', 'DESC');
// one of the two is null
if (null !== $end) {
// end date must be before $end.
$query->where('end_date', '<=', $end->format('Y-m-d 00:00:00'));
}
if (null !== $start) {
// start date must be after $start.
$query->where('start_date', '>=', $start->format('Y-m-d 00:00:00'));
}
$set = $query->get(['budget_limits.*']);
return $set;
}
// when both dates are set:
2018-03-29 12:01:47 -05:00
$set = $budget->budgetlimits()
2016-12-29 13:48:33 -06:00
->where(
function (Builder $q5) use ($start, $end) {
$q5->where(
function (Builder $q1) use ($start, $end) {
// budget limit ends within period
$q1->where(
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', '<=', $end->format('Y-m-d 00:00:00'));
}
)
// budget limit start within period
->orWhere(
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', '<=', $end->format('Y-m-d 00:00:00'));
}
);
2016-12-29 13:48:33 -06:00
}
)
->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'));
}
);
2016-12-30 01:41:48 -06:00
}
)->orderBy('budget_limits.start_date', 'DESC')->get(['budget_limits.*']);
2016-12-29 13:48:33 -06:00
return $set;
}
2016-11-19 06:37:44 -06:00
/**
2016-12-03 14:03:20 -06: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.
2016-11-19 06:37:44 -06:00
*
* @param Collection $budgets
* @param Collection $accounts
* @param Carbon $start
* @param Carbon $end
*
* @return array
2016-11-19 06:37:44 -06:00
*/
public function getBudgetPeriodReport(Collection $budgets, Collection $accounts, Carbon $start, Carbon $end): array
2016-11-19 06:37:44 -06:00
{
2016-11-26 01:41:15 -06:00
$carbonFormat = Navigation::preferredCarbonFormat($start, $end);
2016-12-03 13:38:13 -06:00
$data = [];
// prep data array:
/** @var Budget $budget */
foreach ($budgets as $budget) {
$data[$budget->id] = [
'name' => $budget->name,
'sum' => '0',
'entries' => [],
];
2016-11-19 06:37:44 -06:00
}
2016-12-03 13:38:13 -06:00
// get all transactions:
/** @var JournalCollectorInterface $collector */
$collector = app(JournalCollectorInterface::class);
2016-12-03 13:38:13 -06:00
$collector->setAccounts($accounts)->setRange($start, $end);
$collector->setBudgets($budgets);
$transactions = $collector->getJournals();
2016-12-03 13:38:13 -06:00
// loop transactions:
/** @var Transaction $transaction */
foreach ($transactions as $transaction) {
2018-04-02 08:10:40 -05:00
$budgetId = max((int)$transaction->transaction_journal_budget_id, (int)$transaction->transaction_budget_id);
2016-12-03 13:38:13 -06:00
$date = $transaction->date->format($carbonFormat);
$data[$budgetId]['entries'][$date] = bcadd($data[$budgetId]['entries'][$date] ?? '0', $transaction->transaction_amount);
2016-11-19 06:37:44 -06:00
}
2016-12-03 14:03:20 -06:00
return $data;
2016-11-19 06:37:44 -06:00
}
2016-01-20 08:21:27 -06:00
/**
* @return Collection
*/
public function getBudgets(): Collection
2016-01-20 08:21:27 -06:00
{
/** @var Collection $set */
$set = $this->user->budgets()->get();
2016-01-20 08:21:27 -06:00
$set = $set->sortBy(
function (Budget $budget) {
return strtolower($budget->name);
}
);
return $set;
}
2015-04-05 03:36:28 -05:00
2018-05-07 13:35:14 -05:00
/**
* Get all budgets with these ID's.
*
* @param array $budgetIds
*
* @return Collection
*/
public function getByIds(array $budgetIds): Collection
{
return $this->user->budgets()->whereIn('id', $budgetIds)->get();
}
/**
* @return Collection
*/
public function getInactiveBudgets(): Collection
{
/** @var Collection $set */
$set = $this->user->budgets()->where('active', 0)->get();
$set = $set->sortBy(
function (Budget $budget) {
return strtolower($budget->name);
}
);
return $set;
}
/**
* @param Collection $accounts
* @param Carbon $start
* @param Carbon $end
*
* @return array
*/
public function getNoBudgetPeriodReport(Collection $accounts, Carbon $start, Carbon $end): array
{
$carbonFormat = Navigation::preferredCarbonFormat($start, $end);
/** @var JournalCollectorInterface $collector */
$collector = app(JournalCollectorInterface::class);
$collector->setAccounts($accounts)->setRange($start, $end);
$collector->setTypes([TransactionType::WITHDRAWAL]);
$collector->withoutBudget();
$transactions = $collector->getJournals();
$result = [
'entries' => [],
2018-04-02 08:10:40 -05:00
'name' => (string)trans('firefly.no_budget'),
'sum' => '0',
];
foreach ($transactions as $transaction) {
$date = $transaction->date->format($carbonFormat);
if (!isset($result['entries'][$date])) {
$result['entries'][$date] = '0';
}
$result['entries'][$date] = bcadd($result['entries'][$date], $transaction->transaction_amount);
}
return $result;
}
2016-12-22 09:36:56 -06:00
/**
* @param TransactionCurrency $currency
* @param Carbon $start
* @param Carbon $end
* @param string $amount
*
2018-06-24 01:33:06 -05:00
* @return AvailableBudget
2016-12-22 09:36:56 -06:00
*/
2018-06-24 01:33:06 -05:00
public function setAvailableBudget(TransactionCurrency $currency, Carbon $start, Carbon $end, string $amount): AvailableBudget
2016-12-22 09:36:56 -06:00
{
$availableBudget = $this->user->availableBudgets()
->where('transaction_currency_id', $currency->id)
2018-03-07 13:21:51 -06:00
->where('start_date', $start->format('Y-m-d 00:00:00'))
->where('end_date', $end->format('Y-m-d 00:00:00'))->first();
2017-11-15 05:25:49 -06:00
if (null === $availableBudget) {
2016-12-22 09:36:56 -06:00
$availableBudget = new AvailableBudget;
$availableBudget->user()->associate($this->user);
$availableBudget->transactionCurrency()->associate($currency);
$availableBudget->start_date = $start->format('Y-m-d 00:00:00');
$availableBudget->end_date = $end->format('Y-m-d 00:00:00');
2016-12-22 09:36:56 -06:00
}
$availableBudget->amount = $amount;
$availableBudget->save();
2018-06-24 01:33:06 -05:00
return $availableBudget;
2016-12-22 09:36:56 -06:00
}
2017-01-30 09:46:30 -06:00
/**
* @param User $user
*/
public function setUser(User $user)
{
$this->user = $user;
}
/**
* @param Collection $budgets
* @param Collection $accounts
* @param Carbon $start
* @param Carbon $end
*
* @return string
*/
2017-01-05 02:08:35 -06:00
public function spentInPeriod(Collection $budgets, Collection $accounts, Carbon $start, Carbon $end): string
{
/** @var JournalCollectorInterface $collector */
2017-02-05 09:16:15 -06:00
$collector = app(JournalCollectorInterface::class);
$collector->setUser($this->user);
2017-10-14 00:58:29 -05:00
$collector->setRange($start, $end)->setBudgets($budgets)->withBudgetInformation();
2017-01-05 02:07:56 -06:00
if ($accounts->count() > 0) {
$collector->setAccounts($accounts);
}
2017-11-15 05:25:49 -06:00
if (0 === $accounts->count()) {
$collector->setAllAssetAccounts();
}
$set = $collector->getJournals();
2018-04-27 23:23:13 -05:00
return (string)$set->sum('transaction_amount');
}
2016-05-11 10:17:43 -05:00
/**
* @param Collection $accounts
* @param Carbon $start
* @param Carbon $end
*
* @return string
*/
2017-01-05 02:10:04 -06:00
public function spentInPeriodWoBudget(Collection $accounts, Carbon $start, Carbon $end): string
2016-05-11 10:17:43 -05:00
{
2017-01-05 02:07:56 -06:00
/** @var JournalCollectorInterface $collector */
2017-02-05 09:16:15 -06:00
$collector = app(JournalCollectorInterface::class);
$collector->setUser($this->user);
2017-01-05 02:07:56 -06:00
$collector->setRange($start, $end)->setTypes([TransactionType::WITHDRAWAL])->withoutBudget();
2016-05-11 10:17:43 -05:00
if ($accounts->count() > 0) {
2017-01-05 02:07:56 -06:00
$collector->setAccounts($accounts);
2016-05-11 10:17:43 -05:00
}
2017-11-15 05:25:49 -06:00
if (0 === $accounts->count()) {
2017-01-05 02:07:56 -06:00
$collector->setAllAssetAccounts();
2016-08-02 12:42:41 -05:00
}
2017-01-05 02:07:56 -06:00
$set = $collector->getJournals();
$set = $set->filter(
function (Transaction $transaction) {
if (bccomp($transaction->transaction_amount, '0') === -1) {
return $transaction;
}
return null;
}
);
2018-04-27 23:23:13 -05:00
return (string)$set->sum('transaction_amount');
2016-05-05 23:15:46 -05:00
}
2016-01-20 08:21:27 -06:00
/**
* @param array $data
*
* @return Budget
*/
2016-04-05 15:00:03 -05:00
public function store(array $data): Budget
2016-01-20 08:21:27 -06:00
{
$newBudget = new Budget(
[
2016-10-23 05:19:32 -05:00
'user_id' => $this->user->id,
2016-01-20 08:21:27 -06:00
'name' => $data['name'],
]
);
$newBudget->save();
return $newBudget;
}
2018-06-24 06:20:29 -05:00
/**
* @param array $data
*
* @throws FireflyException
* @return BudgetLimit
*/
public function storeBudgetLimit(array $data): BudgetLimit
{
$this->cleanupBudgets();
/** @var Budget $budget */
$budget = $data['budget'];
// find limit with same date range.
// if it exists, throw error.
$limits = $budget->budgetlimits()
->where('budget_limits.start_date', $data['start_date']->format('Y-m-d 00:00:00'))
->where('budget_limits.end_date', $data['end_date']->format('Y-m-d 00:00:00'))
->get(['budget_limits.*'])->count();
Log::debug(sprintf('Found %d budget limits.', $limits));
if ($limits > 0) {
throw new FireflyException('A budget limit for this budget, and this date range already exists. You must update the existing one.');
}
Log::debug('No existing budget limit, create a new one');
// or create one and return it.
$limit = new BudgetLimit;
$limit->budget()->associate($budget);
$limit->start_date = $data['start_date']->format('Y-m-d 00:00:00');
$limit->end_date = $data['end_date']->format('Y-m-d 00:00:00');
$limit->amount = $data['amount'];
$limit->save();
Log::debug(sprintf('Created new budget limit with ID #%d and amount %s', $limit->id, $data['amount']));
return $limit;
}
2016-01-20 08:21:27 -06:00
/**
* @param Budget $budget
* @param array $data
*
* @return Budget
*/
2016-04-05 15:00:03 -05:00
public function update(Budget $budget, array $data): Budget
2016-01-20 08:21:27 -06:00
{
// update the account:
$budget->name = $data['name'];
$budget->active = $data['active'];
$budget->save();
return $budget;
}
2018-06-24 01:33:06 -05:00
/**
* @param AvailableBudget $availableBudget
* @param array $data
*
* @return AvailableBudget
* @throws FireflyException
*/
public function updateAvailableBudget(AvailableBudget $availableBudget, array $data): AvailableBudget
{
$existing = $this->user->availableBudgets()
->where('transaction_currency_id', $data['transaction_currency_id'])
->where('start_date', $data['start_date']->format('Y-m-d 00:00:00'))
->where('end_date', $data['end_date']->format('Y-m-d 00:00:00'))
->where('id', '!=', $availableBudget->id)
->first();
if (null !== $existing) {
throw new FireflyException(sprintf('An entry already exists for these parameters: available budget object with ID #%d', $existing->id));
}
$availableBudget->transaction_currency_id = $data['transaction_currency_id'];
$availableBudget->start_date = $data['start_date'];
$availableBudget->end_date = $data['end_date'];
$availableBudget->amount = $data['amount'];
$availableBudget->save();
return $availableBudget;
}
2018-06-24 06:20:29 -05:00
/**
* @param BudgetLimit $budgetLimit
* @param array $data
*
* @return BudgetLimit
* @throws Exception
*/
public function updateBudgetLimit(BudgetLimit $budgetLimit, array $data): BudgetLimit
{
$this->cleanupBudgets();
/** @var Budget $budget */
$budget = $data['budget'];
$budgetLimit->budget()->associate($budget);
$budgetLimit->start_date = $data['start_date']->format('Y-m-d 00:00:00');
$budgetLimit->end_date = $data['end_date']->format('Y-m-d 00:00:00');
$budgetLimit->amount = $data['amount'];
$budgetLimit->save();
Log::debug(sprintf('Updated budget limit with ID #%d and amount %s', $budgetLimit->id, $data['amount']));
return $budgetLimit;
}
2016-01-20 08:21:27 -06:00
/**
* @param Budget $budget
2016-04-28 03:59:36 -05:00
* @param Carbon $start
* @param Carbon $end
2017-11-18 04:32:35 -06:00
* @param string $amount
2016-01-20 08:21:27 -06:00
*
* @return BudgetLimit|null
2016-01-20 08:21:27 -06:00
*/
public function updateLimitAmount(Budget $budget, Carbon $start, Carbon $end, string $amount): ?BudgetLimit
2016-01-20 08:21:27 -06:00
{
2018-02-19 13:32:33 -06:00
$this->cleanupBudgets();
2017-10-15 07:38:12 -05:00
// count the limits:
$limits = $budget->budgetlimits()
->where('budget_limits.start_date', $start->format('Y-m-d 00:00:00'))
->where('budget_limits.end_date', $end->format('Y-m-d 00:00:00'))
2017-10-15 07:38:12 -05:00
->get(['budget_limits.*'])->count();
2017-11-24 10:05:22 -06:00
Log::debug(sprintf('Found %d budget limits.', $limits));
2016-12-29 13:52:02 -06:00
// there might be a budget limit for these dates:
2016-01-20 08:21:27 -06:00
/** @var BudgetLimit $limit */
2016-04-28 03:59:36 -05:00
$limit = $budget->budgetlimits()
->where('budget_limits.start_date', $start->format('Y-m-d 00:00:00'))
->where('budget_limits.end_date', $end->format('Y-m-d 00:00:00'))
2016-12-29 13:52:02 -06:00
->first(['budget_limits.*']);
2016-04-28 03:59:36 -05:00
2017-10-15 07:38:12 -05:00
// if more than 1 limit found, delete the others:
2017-11-15 05:25:49 -06:00
if ($limits > 1 && null !== $limit) {
2017-11-24 10:05:22 -06:00
Log::debug(sprintf('Found more than 1, delete all except #%d', $limit->id));
2017-10-15 07:38:12 -05:00
$budget->budgetlimits()
->where('budget_limits.start_date', $start->format('Y-m-d 00:00:00'))
->where('budget_limits.end_date', $end->format('Y-m-d 00:00:00'))
2017-10-15 07:38:12 -05:00
->where('budget_limits.id', '!=', $limit->id)->delete();
}
2016-04-28 03:59:36 -05:00
// delete if amount is zero.
2017-11-24 10:05:22 -06:00
// Returns 0 if the two operands are equal,
// 1 if the left_operand is larger than the right_operand, -1 otherwise.
if (null !== $limit && bccomp($amount, '0') <= 0) {
Log::debug(sprintf('%s is zero, delete budget limit #%d', $amount, $limit->id));
try {
$limit->delete();
} catch (Exception $e) {
Log::debug(sprintf('Could not delete limit: %s', $e->getMessage()));
}
2016-04-28 03:59:36 -05:00
return null;
2016-04-28 03:59:36 -05:00
}
// update if exists:
2017-11-15 05:25:49 -06:00
if (null !== $limit) {
2017-11-24 10:05:22 -06:00
Log::debug(sprintf('Existing budget limit is #%d, update this to amount %s', $limit->id, $amount));
2016-04-28 03:59:36 -05:00
$limit->amount = $amount;
2016-01-20 08:21:27 -06:00
$limit->save();
2016-04-28 03:59:36 -05:00
return $limit;
2016-01-20 08:21:27 -06:00
}
2017-11-24 10:05:22 -06:00
Log::debug('No existing budget limit, create a new one');
2016-12-29 13:52:02 -06:00
// or create one and return it.
2016-04-28 03:59:36 -05:00
$limit = new BudgetLimit;
$limit->budget()->associate($budget);
$limit->start_date = $start->format('Y-m-d 00:00:00');
$limit->end_date = $end->format('Y-m-d 00:00:00');
2016-12-29 13:52:02 -06:00
$limit->amount = $amount;
2016-04-28 03:59:36 -05:00
$limit->save();
2017-11-24 10:05:22 -06:00
Log::debug(sprintf('Created new budget limit with ID #%d and amount %s', $limit->id, $amount));
2016-04-28 03:59:36 -05:00
2016-01-20 08:21:27 -06:00
return $limit;
}
2015-03-29 01:14:32 -05:00
}