Refactor code to traits.

This commit is contained in:
James Cole
2018-12-31 08:11:57 +01:00
parent e7bcc01fe8
commit f80de95bb0
14 changed files with 972 additions and 922 deletions

View File

@@ -476,154 +476,4 @@ class BudgetController extends Controller
} }
/**
* Get the amount of money budgeted in a period.
*
* @param Budget $budget
* @param Carbon $start
* @param Carbon $end
*
* @return array
*/
protected function getBudgetedInPeriod(Budget $budget, Carbon $start, Carbon $end): array // get data + augment with info
{
$key = app('navigation')->preferredCarbonFormat($start, $end);
$range = app('navigation')->preferredRangeFormat($start, $end);
$current = clone $start;
$budgeted = [];
while ($current < $end) {
/** @var Carbon $currentStart */
$currentStart = app('navigation')->startOfPeriod($current, $range);
/** @var Carbon $currentEnd */
$currentEnd = app('navigation')->endOfPeriod($current, $range);
$budgetLimits = $this->repository->getBudgetLimits($budget, $currentStart, $currentEnd);
$index = $currentStart->format($key);
$budgeted[$index] = $budgetLimits->sum('amount');
$currentEnd->addDay();
$current = clone $currentEnd;
}
return $budgeted;
}
/**
* Get the expenses for a budget in a date range.
*
* @param Collection $limits
* @param Budget $budget
* @param Carbon $start
* @param Carbon $end
*
* @return array
*
* @SuppressWarnings(PHPMD.CyclomaticComplexity)
*/
protected function getExpensesForBudget(Collection $limits, Budget $budget, Carbon $start, Carbon $end): array // get data + augment with info
{
$return = [];
if (0 === $limits->count()) {
$spent = $this->repository->spentInPeriod(new Collection([$budget]), new Collection, $start, $end);
if (0 !== bccomp($spent, '0')) {
$return[$budget->name]['spent'] = bcmul($spent, '-1');
$return[$budget->name]['left'] = 0;
$return[$budget->name]['overspent'] = 0;
}
return $return;
}
$rows = $this->spentInPeriodMulti($budget, $limits);
foreach ($rows as $name => $row) {
if (0 !== bccomp($row['spent'], '0') || 0 !== bccomp($row['left'], '0')) {
$return[$name] = $row;
}
}
unset($rows);
return $return;
}
/**
*
* Returns an array with the following values:
* 0 =>
* 'name' => name of budget + repetition
* 'left' => left in budget repetition (always zero)
* 'overspent' => spent more than budget repetition? (always zero)
* 'spent' => actually spent in period for budget
* 1 => (etc)
*
* @param Budget $budget
* @param Collection $limits
*
* @return array
*
* @SuppressWarnings(PHPMD.CyclomaticComplexity)
*
*/
protected function spentInPeriodMulti(Budget $budget, Collection $limits): array // get data + augment with info
{
$return = [];
$format = (string)trans('config.month_and_day');
$name = $budget->name;
/** @var BudgetLimit $budgetLimit */
foreach ($limits as $budgetLimit) {
$expenses = $this->repository->spentInPeriod(new Collection([$budget]), new Collection, $budgetLimit->start_date, $budgetLimit->end_date);
$expenses = app('steam')->positive($expenses);
if ($limits->count() > 1) {
$name = $budget->name . ' ' . trans(
'firefly.between_dates',
[
'start' => $budgetLimit->start_date->formatLocalized($format),
'end' => $budgetLimit->end_date->formatLocalized($format),
]
);
}
$amount = $budgetLimit->amount;
$leftInLimit = bcsub($amount, $expenses);
$hasOverspent = bccomp($leftInLimit, '0') === -1;
$left = $hasOverspent ? '0' : bcsub($amount, $expenses);
$spent = $hasOverspent ? $amount : $expenses;
$overspent = $hasOverspent ? app('steam')->positive($leftInLimit) : '0';
$return[$name] = [
'left' => $left,
'overspent' => $overspent,
'spent' => $spent,
];
}
return $return;
}
/**
* Returns an array with the following values:
* 'name' => "no budget" in local language
* 'repetition_left' => left in budget repetition (always zero)
* 'repetition_overspent' => spent more than budget repetition? (always zero)
* 'spent' => actually spent in period for budget.
*
* @param Carbon $start
* @param Carbon $end
*
* @return string
*/
protected function spentInPeriodWithout(Carbon $start, Carbon $end): string // get data + augment with info
{
// collector
/** @var TransactionCollectorInterface $collector */
$collector = app(TransactionCollectorInterface::class);
$types = [TransactionType::WITHDRAWAL];
$collector->setAllAssetAccounts()->setTypes($types)->setRange($start, $end)->withoutBudget();
$transactions = $collector->getTransactions();
$sum = '0';
/** @var Transaction $entry */
foreach ($transactions as $entry) {
$sum = bcadd($entry->transaction_amount, $sum);
}
return $sum;
}
} }

View File

@@ -33,6 +33,7 @@ use FireflyIII\Repositories\Category\CategoryRepositoryInterface;
use FireflyIII\Repositories\Currency\CurrencyRepositoryInterface; use FireflyIII\Repositories\Currency\CurrencyRepositoryInterface;
use FireflyIII\Support\CacheProperties; use FireflyIII\Support\CacheProperties;
use FireflyIII\Support\Http\Controllers\AugumentData; use FireflyIII\Support\Http\Controllers\AugumentData;
use FireflyIII\Support\Http\Controllers\ChartGeneration;
use FireflyIII\Support\Http\Controllers\DateCalculation; use FireflyIII\Support\Http\Controllers\DateCalculation;
use Illuminate\Http\JsonResponse; use Illuminate\Http\JsonResponse;
use Illuminate\Support\Collection; use Illuminate\Support\Collection;
@@ -43,7 +44,7 @@ use Log;
*/ */
class CategoryController extends Controller class CategoryController extends Controller
{ {
use DateCalculation, AugumentData; use DateCalculation, AugumentData, ChartGeneration;
/** @var GeneratorInterface Chart generation methods. */ /** @var GeneratorInterface Chart generation methods. */
protected $generator; protected $generator;
@@ -393,91 +394,4 @@ class CategoryController extends Controller
return response()->json($data); return response()->json($data);
} }
/**
* Chart for a specific period (start and end).
*
*
* @param Category $category
* @param Carbon $start
* @param Carbon $end
*
* @return array
*
* @SuppressWarnings(PHPMD.ExcessiveMethodLength)
*/
protected function makePeriodChart(Category $category, Carbon $start, Carbon $end): array // chart helper method.
{
$cache = new CacheProperties;
$cache->addProperty($start);
$cache->addProperty($end);
$cache->addProperty($category->id);
$cache->addProperty('chart.category.period-chart');
if ($cache->has()) {
//return $cache->get(); // @codeCoverageIgnore
}
/** @var AccountRepositoryInterface $accountRepository */
$accountRepository = app(AccountRepositoryInterface::class);
$accounts = $accountRepository->getAccountsByType([AccountType::DEFAULT, AccountType::ASSET]);
$repository = app(CategoryRepositoryInterface::class);
// chart data
$chartData = [
[
'label' => (string)trans('firefly.spent'),
'entries' => [],
'type' => 'bar',
'backgroundColor' => 'rgba(219, 68, 55, 0.5)', // red
],
[
'label' => (string)trans('firefly.earned'),
'entries' => [],
'type' => 'bar',
'backgroundColor' => 'rgba(0, 141, 76, 0.5)', // green
],
[
'label' => (string)trans('firefly.sum'),
'entries' => [],
'type' => 'line',
'fill' => false,
],
];
$step = $this->calculateStep($start, $end);
while ($start <= $end) {
$spent = $repository->spentInPeriod(new Collection([$category]), $accounts, $start, $start);
$earned = $repository->earnedInPeriod(new Collection([$category]), $accounts, $start, $start);
$sum = bcadd($spent, $earned);
$label = trim(app('navigation')->periodShow($start, $step));
$chartData[0]['entries'][$label] = round(bcmul($spent, '-1'), 12);
$chartData[1]['entries'][$label] = round($earned, 12);
$chartData[2]['entries'][$label] = round($sum, 12);
switch ($step) {
default:
case '1D':
$start->addDay();
break;
case '1W':
$start->addDays(7);
break;
case '1M':
$start->addMonth();
break;
case '1Y':
$start->addYear();
break;
}
}
$data = $this->generator->multiSet($chartData);
$cache->store($data);
return $data;
}
} }

View File

@@ -28,9 +28,9 @@ use FireflyIII\Helpers\Report\NetWorthInterface;
use FireflyIII\Http\Controllers\Controller; use FireflyIII\Http\Controllers\Controller;
use FireflyIII\Models\Account; use FireflyIII\Models\Account;
use FireflyIII\Repositories\Account\AccountRepositoryInterface; use FireflyIII\Repositories\Account\AccountRepositoryInterface;
use FireflyIII\Repositories\Account\AccountTaskerInterface;
use FireflyIII\Support\CacheProperties; use FireflyIII\Support\CacheProperties;
use FireflyIII\Support\Http\Controllers\BasicDataSupport; use FireflyIII\Support\Http\Controllers\BasicDataSupport;
use FireflyIII\Support\Http\Controllers\ChartGeneration;
use Illuminate\Http\JsonResponse; use Illuminate\Http\JsonResponse;
use Illuminate\Support\Collection; use Illuminate\Support\Collection;
use Log; use Log;
@@ -40,7 +40,7 @@ use Log;
*/ */
class ReportController extends Controller class ReportController extends Controller
{ {
use BasicDataSupport; use BasicDataSupport, ChartGeneration;
/** @var GeneratorInterface Chart generation methods. */ /** @var GeneratorInterface Chart generation methods. */
protected $generator; protected $generator;
@@ -84,7 +84,7 @@ class ReportController extends Controller
// filter accounts on having the preference for being included. // filter accounts on having the preference for being included.
/** @var AccountRepositoryInterface $accountRepository */ /** @var AccountRepositoryInterface $accountRepository */
$accountRepository = app(AccountRepositoryInterface::class); $accountRepository = app(AccountRepositoryInterface::class);
$filtered = $accounts->filter( $filtered = $accounts->filter(
function (Account $account) use ($accountRepository) { function (Account $account) use ($accountRepository) {
$includeNetWorth = $accountRepository->getMetaValue($account, 'include_net_worth'); $includeNetWorth = $accountRepository->getMetaValue($account, 'include_net_worth');
$result = null === $includeNetWorth ? true : '1' === $includeNetWorth; $result = null === $includeNetWorth ? true : '1' === $includeNetWorth;
@@ -97,7 +97,6 @@ class ReportController extends Controller
); );
while ($current < $end) { while ($current < $end) {
// get balances by date, grouped by currency. // get balances by date, grouped by currency.
$result = $helper->getNetWorthByCurrency($filtered, $current); $result = $helper->getNetWorthByCurrency($filtered, $current);
@@ -263,67 +262,4 @@ class ReportController extends Controller
return response()->json($data); return response()->json($data);
} }
/**
* Collects the incomes and expenses for the given periods, grouped per month. Will cache its results.
*
* @param Collection $accounts
* @param Carbon $start
* @param Carbon $end
*
* @return array
*
* @SuppressWarnings(PHPMD.ExcessiveMethodLength)
*/
protected function getChartData(Collection $accounts, Carbon $start, Carbon $end): array // chart helper function
{
$cache = new CacheProperties;
$cache->addProperty('chart.report.get-chart-data');
$cache->addProperty($start);
$cache->addProperty($accounts);
$cache->addProperty($end);
if ($cache->has()) {
return $cache->get(); // @codeCoverageIgnore
}
$currentStart = clone $start;
$spentArray = [];
$earnedArray = [];
/** @var AccountTaskerInterface $tasker */
$tasker = app(AccountTaskerInterface::class);
while ($currentStart <= $end) {
$currentEnd = app('navigation')->endOfPeriod($currentStart, '1M');
$earned = (string)array_sum(
array_map(
function ($item) {
return $item['sum'];
},
$tasker->getIncomeReport($currentStart, $currentEnd, $accounts)
)
);
$spent = (string)array_sum(
array_map(
function ($item) {
return $item['sum'];
},
$tasker->getExpenseReport($currentStart, $currentEnd, $accounts)
)
);
$label = $currentStart->format('Y-m') . '-01';
$spentArray[$label] = bcmul($spent, '-1');
$earnedArray[$label] = $earned;
$currentStart = app('navigation')->addPeriod($currentStart, '1M', 0);
}
$result = [
'spent' => $spentArray,
'earned' => $earnedArray,
];
$cache->store($result);
return $result;
}
} }

View File

@@ -340,294 +340,4 @@ class ExpenseController extends Controller
} }
/** @noinspection MoreThanThreeArgumentsInspection */
/**
* Group by category (earnings).
*
* @param Collection $assets
* @param Collection $opposing
* @param Carbon $start
* @param Carbon $end
*
* @return array
*
* @SuppressWarnings(PHPMD.CyclomaticComplexity)
* @SuppressWarnings(PHPMD.ExcessiveMethodLength)
*/
protected function earnedByCategory(Collection $assets, Collection $opposing, Carbon $start, Carbon $end): array // get data + augment with info
{
/** @var TransactionCollectorInterface $collector */
$collector = app(TransactionCollectorInterface::class);
$collector->setRange($start, $end)->setTypes([TransactionType::DEPOSIT])->setAccounts($assets);
$collector->setOpposingAccounts($opposing)->withCategoryInformation();
$set = $collector->getTransactions();
$sum = [];
// loop to support multi currency
foreach ($set as $transaction) {
$currencyId = $transaction->transaction_currency_id;
$categoryName = $transaction->transaction_category_name;
$categoryId = (int)$transaction->transaction_category_id;
// if null, grab from journal:
if (0 === $categoryId) {
$categoryName = $transaction->transaction_journal_category_name;
$categoryId = (int)$transaction->transaction_journal_category_id;
}
if (0 !== $categoryId) {
$categoryName = app('steam')->tryDecrypt($categoryName);
}
// if not set, set to zero:
if (!isset($sum[$categoryId][$currencyId])) {
$sum[$categoryId] = [
'grand_total' => '0',
'name' => $categoryName,
'per_currency' => [
$currencyId => [
'sum' => '0',
'category' => [
'id' => $categoryId,
'name' => $categoryName,
],
'currency' => [
'symbol' => $transaction->transaction_currency_symbol,
'dp' => $transaction->transaction_currency_dp,
],
],
],
];
}
// add amount
$sum[$categoryId]['per_currency'][$currencyId]['sum'] = bcadd(
$sum[$categoryId]['per_currency'][$currencyId]['sum'], $transaction->transaction_amount
);
$sum[$categoryId]['grand_total'] = bcadd($sum[$categoryId]['grand_total'], $transaction->transaction_amount);
}
return $sum;
}
/** @noinspection MoreThanThreeArgumentsInspection */
/**
* Earned in period for accounts.
*
* @param Collection $assets
* @param Collection $opposing
* @param Carbon $start
* @param Carbon $end
*
* @return array
*/
protected function earnedInPeriod(Collection $assets, Collection $opposing, Carbon $start, Carbon $end): array // get data + augment with info
{
/** @var TransactionCollectorInterface $collector */
$collector = app(TransactionCollectorInterface::class);
$collector->setRange($start, $end)->setTypes([TransactionType::DEPOSIT])->setAccounts($assets);
$collector->setOpposingAccounts($opposing);
$set = $collector->getTransactions();
$sum = [
'grand_sum' => '0',
'per_currency' => [],
];
// loop to support multi currency
foreach ($set as $transaction) {
$currencyId = $transaction->transaction_currency_id;
// if not set, set to zero:
if (!isset($sum['per_currency'][$currencyId])) {
$sum['per_currency'][$currencyId] = [
'sum' => '0',
'currency' => [
'symbol' => $transaction->transaction_currency_symbol,
'dp' => $transaction->transaction_currency_dp,
],
];
}
// add amount
$sum['per_currency'][$currencyId]['sum'] = bcadd($sum['per_currency'][$currencyId]['sum'], $transaction->transaction_amount);
$sum['grand_sum'] = bcadd($sum['grand_sum'], $transaction->transaction_amount);
}
return $sum;
}
/** @noinspection MoreThanThreeArgumentsInspection */
/**
* Spent by budget.
*
* @param Collection $assets
* @param Collection $opposing
* @param Carbon $start
* @param Carbon $end
*
* @return array
* @SuppressWarnings(PHPMD.CyclomaticComplexity)
* @SuppressWarnings(PHPMD.ExcessiveMethodLength)
*/
protected function spentByBudget(Collection $assets, Collection $opposing, Carbon $start, Carbon $end): array // get data + augment with info
{
/** @var TransactionCollectorInterface $collector */
$collector = app(TransactionCollectorInterface::class);
$collector->setRange($start, $end)->setTypes([TransactionType::WITHDRAWAL])->setAccounts($assets);
$collector->setOpposingAccounts($opposing)->withBudgetInformation();
$set = $collector->getTransactions();
$sum = [];
// loop to support multi currency
foreach ($set as $transaction) {
$currencyId = $transaction->transaction_currency_id;
$budgetName = $transaction->transaction_budget_name;
$budgetId = (int)$transaction->transaction_budget_id;
// if null, grab from journal:
if (0 === $budgetId) {
$budgetName = $transaction->transaction_journal_budget_name;
$budgetId = (int)$transaction->transaction_journal_budget_id;
}
if (0 !== $budgetId) {
$budgetName = app('steam')->tryDecrypt($budgetName);
}
// if not set, set to zero:
if (!isset($sum[$budgetId][$currencyId])) {
$sum[$budgetId] = [
'grand_total' => '0',
'name' => $budgetName,
'per_currency' => [
$currencyId => [
'sum' => '0',
'budget' => [
'id' => $budgetId,
'name' => $budgetName,
],
'currency' => [
'symbol' => $transaction->transaction_currency_symbol,
'dp' => $transaction->transaction_currency_dp,
],
],
],
];
}
// add amount
$sum[$budgetId]['per_currency'][$currencyId]['sum'] = bcadd(
$sum[$budgetId]['per_currency'][$currencyId]['sum'], $transaction->transaction_amount
);
$sum[$budgetId]['grand_total'] = bcadd($sum[$budgetId]['grand_total'], $transaction->transaction_amount);
}
return $sum;
}
/** @noinspection MoreThanThreeArgumentsInspection */
/**
* Spent by category.
*
* @param Collection $assets
* @param Collection $opposing
* @param Carbon $start
* @param Carbon $end
*
* @return array
*
* @SuppressWarnings(PHPMD.CyclomaticComplexity)
* @SuppressWarnings(PHPMD.ExcessiveMethodLength)
*/
protected function spentByCategory(Collection $assets, Collection $opposing, Carbon $start, Carbon $end): array // get data + augment with info
{
/** @var TransactionCollectorInterface $collector */
$collector = app(TransactionCollectorInterface::class);
$collector->setRange($start, $end)->setTypes([TransactionType::WITHDRAWAL])->setAccounts($assets);
$collector->setOpposingAccounts($opposing)->withCategoryInformation();
$set = $collector->getTransactions();
$sum = [];
// loop to support multi currency
foreach ($set as $transaction) {
$currencyId = $transaction->transaction_currency_id;
$categoryName = $transaction->transaction_category_name;
$categoryId = (int)$transaction->transaction_category_id;
// if null, grab from journal:
if (0 === $categoryId) {
$categoryName = $transaction->transaction_journal_category_name;
$categoryId = (int)$transaction->transaction_journal_category_id;
}
if (0 !== $categoryId) {
$categoryName = app('steam')->tryDecrypt($categoryName);
}
// if not set, set to zero:
if (!isset($sum[$categoryId][$currencyId])) {
$sum[$categoryId] = [
'grand_total' => '0',
'name' => $categoryName,
'per_currency' => [
$currencyId => [
'sum' => '0',
'category' => [
'id' => $categoryId,
'name' => $categoryName,
],
'currency' => [
'symbol' => $transaction->transaction_currency_symbol,
'dp' => $transaction->transaction_currency_dp,
],
],
],
];
}
// add amount
$sum[$categoryId]['per_currency'][$currencyId]['sum'] = bcadd(
$sum[$categoryId]['per_currency'][$currencyId]['sum'], $transaction->transaction_amount
);
$sum[$categoryId]['grand_total'] = bcadd($sum[$categoryId]['grand_total'], $transaction->transaction_amount);
}
return $sum;
}
/** @noinspection MoreThanThreeArgumentsInspection */
/**
* Spent in a period.
*
* @param Collection $assets
* @param Collection $opposing
* @param Carbon $start
* @param Carbon $end
*
* @return array
*/
protected function spentInPeriod(Collection $assets, Collection $opposing, Carbon $start, Carbon $end): array // get data + augment with info
{
/** @var TransactionCollectorInterface $collector */
$collector = app(TransactionCollectorInterface::class);
$collector->setRange($start, $end)->setTypes([TransactionType::WITHDRAWAL])->setAccounts($assets);
$collector->setOpposingAccounts($opposing);
$set = $collector->getTransactions();
$sum = [
'grand_sum' => '0',
'per_currency' => [],
];
// loop to support multi currency
foreach ($set as $transaction) {
$currencyId = (int)$transaction->transaction_currency_id;
// if not set, set to zero:
if (!isset($sum['per_currency'][$currencyId])) {
$sum['per_currency'][$currencyId] = [
'sum' => '0',
'currency' => [
'symbol' => $transaction->transaction_currency_symbol,
'dp' => $transaction->transaction_currency_dp,
],
];
}
// add amount
$sum['per_currency'][$currencyId]['sum'] = bcadd($sum['per_currency'][$currencyId]['sum'], $transaction->transaction_amount);
$sum['grand_sum'] = bcadd($sum['grand_sum'], $transaction->transaction_amount);
}
return $sum;
}
} }

View File

@@ -29,6 +29,8 @@ use FireflyIII\Http\Requests\RuleFormRequest;
use FireflyIII\Models\Bill; use FireflyIII\Models\Bill;
use FireflyIII\Models\RuleGroup; use FireflyIII\Models\RuleGroup;
use FireflyIII\Repositories\Rule\RuleRepositoryInterface; use FireflyIII\Repositories\Rule\RuleRepositoryInterface;
use FireflyIII\Support\Http\Controllers\AugumentData;
use FireflyIII\Support\Http\Controllers\ModelInformation;
use FireflyIII\Support\Http\Controllers\RuleManagement; use FireflyIII\Support\Http\Controllers\RuleManagement;
use Illuminate\Http\RedirectResponse; use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request; use Illuminate\Http\Request;
@@ -40,7 +42,7 @@ use Throwable;
*/ */
class CreateController extends Controller class CreateController extends Controller
{ {
use RuleManagement; use RuleManagement, ModelInformation;
/** @var RuleRepositoryInterface Rule repository */ /** @var RuleRepositoryInterface Rule repository */
private $ruleRepos; private $ruleRepos;
@@ -199,78 +201,4 @@ class CreateController extends Controller
return $redirect; return $redirect;
} }
/**
* Get actions based on a bill.
*
* @param Bill $bill
*
* @return array
*/
protected function getActionsForBill(Bill $bill): array // get info and augument
{
try {
$result = view(
'rules.partials.action',
[
'oldAction' => 'link_to_bill',
'oldValue' => $bill->name,
'oldChecked' => false,
'count' => 1,
]
)->render();
// @codeCoverageIgnoreStart
} catch (Throwable $e) {
Log::error(sprintf('Throwable was thrown in getActionsForBill(): %s', $e->getMessage()));
Log::error($e->getTraceAsString());
$result = 'Could not render view. See log files.';
}
// @codeCoverageIgnoreEnd
return [$result];
}
/**
* Create fake triggers to match the bill's properties
*
* @param Bill $bill
*
* @return array
*/
protected function getTriggersForBill(Bill $bill): array // get info and augument
{
$result = [];
$triggers = ['currency_is', 'amount_more', 'amount_less', 'description_contains'];
$values = [
$bill->transactionCurrency()->first()->name,
round((float)$bill->amount_min, 12),
round((float)$bill->amount_max, 12),
$bill->name,
];
foreach ($triggers as $index => $trigger) {
try {
$string = view(
'rules.partials.trigger',
[
'oldTrigger' => $trigger,
'oldValue' => $values[$index],
'oldChecked' => false,
'count' => $index + 1,
]
)->render();
// @codeCoverageIgnoreStart
} catch (Throwable $e) {
Log::debug(sprintf('Throwable was thrown in getTriggersForBill(): %s', $e->getMessage()));
Log::debug($e->getTraceAsString());
$string = '';
// @codeCoverageIgnoreEnd
}
if ('' !== $string) {
$result[] = $string;
}
}
return $result;
}
} }

View File

@@ -30,6 +30,8 @@ use FireflyIII\Models\Rule;
use FireflyIII\Models\RuleAction; use FireflyIII\Models\RuleAction;
use FireflyIII\Models\RuleTrigger; use FireflyIII\Models\RuleTrigger;
use FireflyIII\Repositories\Rule\RuleRepositoryInterface; use FireflyIII\Repositories\Rule\RuleRepositoryInterface;
use FireflyIII\Support\Http\Controllers\ModelInformation;
use FireflyIII\Support\Http\Controllers\RenderPartialViews;
use FireflyIII\Support\Http\Controllers\RuleManagement; use FireflyIII\Support\Http\Controllers\RuleManagement;
use Illuminate\Http\RedirectResponse; use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request; use Illuminate\Http\Request;
@@ -41,7 +43,7 @@ use Throwable;
*/ */
class EditController extends Controller class EditController extends Controller
{ {
use RuleManagement; use RuleManagement, RenderPartialViews;
/** @var RuleRepositoryInterface Rule repository */ /** @var RuleRepositoryInterface Rule repository */
private $ruleRepos; private $ruleRepos;
@@ -146,81 +148,4 @@ class EditController extends Controller
return $redirect; return $redirect;
} }
/**
* Get current (from system) rule actions.
*
* @param Rule $rule
*
* @return array
*/
protected function getCurrentActions(Rule $rule): array // get info from object and present.
{
$index = 0;
$actions = [];
$currentActions = $rule->ruleActions()->orderBy('order','ASC')->get();
/** @var RuleAction $entry */
foreach ($currentActions as $entry) {
$count = ($index + 1);
try {
$actions[] = view(
'rules.partials.action',
[
'oldAction' => $entry->action_type,
'oldValue' => $entry->action_value,
'oldChecked' => $entry->stop_processing,
'count' => $count,
]
)->render();
// @codeCoverageIgnoreStart
} catch (Throwable $e) {
Log::debug(sprintf('Throwable was thrown in getCurrentActions(): %s', $e->getMessage()));
Log::error($e->getTraceAsString());
}
// @codeCoverageIgnoreEnd
++$index;
}
return $actions;
}
/**
* Get current (from DB) rule triggers.
*
* @param Rule $rule
*
* @return array
*
*/
protected function getCurrentTriggers(Rule $rule): array // get info from object and present.
{
$index = 0;
$triggers = [];
$currentTriggers = $rule->ruleTriggers()->orderBy('order','ASC')->get();
/** @var RuleTrigger $entry */
foreach ($currentTriggers as $entry) {
if ('user_action' !== $entry->trigger_type) {
$count = ($index + 1);
try {
$triggers[] = view(
'rules.partials.trigger',
[
'oldTrigger' => $entry->trigger_type,
'oldValue' => $entry->trigger_value,
'oldChecked' => $entry->stop_processing,
'count' => $count,
]
)->render();
// @codeCoverageIgnoreStart
} catch (Throwable $e) {
Log::debug(sprintf('Throwable was thrown in getCurrentTriggers(): %s', $e->getMessage()));
Log::error($e->getTraceAsString());
}
// @codeCoverageIgnoreEnd
++$index;
}
}
return $triggers;
}
} }

View File

@@ -25,10 +25,8 @@ namespace FireflyIII\Http\Controllers\Transaction;
use FireflyIII\Events\UpdatedTransactionJournal; use FireflyIII\Events\UpdatedTransactionJournal;
use FireflyIII\Exceptions\FireflyException; use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Http\Controllers\Controller; use FireflyIII\Http\Controllers\Controller;
use FireflyIII\Models\Account;
use FireflyIII\Models\TransactionJournal; use FireflyIII\Models\TransactionJournal;
use FireflyIII\Models\TransactionType; use FireflyIII\Models\TransactionType;
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
use FireflyIII\Repositories\Journal\JournalRepositoryInterface; use FireflyIII\Repositories\Journal\JournalRepositoryInterface;
use FireflyIII\Support\Http\Controllers\ModelInformation; use FireflyIII\Support\Http\Controllers\ModelInformation;
use Illuminate\Http\Request; use Illuminate\Http\Request;
@@ -163,6 +161,7 @@ class ConvertController extends Controller
if ($errors->count() > 0) { if ($errors->count() > 0) {
Log::error('Errors while converting: ', $errors->toArray()); Log::error('Errors while converting: ', $errors->toArray());
return redirect(route('transactions.convert.index', [strtolower($destinationType->type), $journal->id]))->withErrors($errors)->withInput(); return redirect(route('transactions.convert.index', [strtolower($destinationType->type), $journal->id]))->withErrors($errors)->withInput();
} }
@@ -174,126 +173,4 @@ class ConvertController extends Controller
return redirect(route('transactions.show', [$journal->id])); return redirect(route('transactions.show', [$journal->id]));
} }
/**
* Get the destination account. Is complex.
*
* @param TransactionJournal $journal
* @param TransactionType $destinationType
* @param array $data
*
* @return Account
*
* @throws FireflyException
*
* @SuppressWarnings(PHPMD.ExcessiveMethodLength)
* @SuppressWarnings(PHPMD.CyclomaticComplexity)
*/
protected function getDestinationAccount(TransactionJournal $journal, TransactionType $destinationType, array $data
): Account // helper for conversion. Get info from obj.
{
/** @var AccountRepositoryInterface $accountRepository */
$accountRepository = app(AccountRepositoryInterface::class);
$sourceAccount = $this->repository->getJournalSourceAccounts($journal)->first();
$destinationAccount = $this->repository->getJournalDestinationAccounts($journal)->first();
$sourceType = $journal->transactionType;
$joined = $sourceType->type . '-' . $destinationType->type;
switch ($joined) {
default:
throw new FireflyException('Cannot handle ' . $joined); // @codeCoverageIgnore
case TransactionType::WITHDRAWAL . '-' . TransactionType::DEPOSIT:
// one
$destination = $sourceAccount;
break;
case TransactionType::WITHDRAWAL . '-' . TransactionType::TRANSFER:
// two
$destination = $accountRepository->findNull((int)$data['destination_account_asset']);
break;
case TransactionType::DEPOSIT . '-' . TransactionType::WITHDRAWAL:
case TransactionType::TRANSFER . '-' . TransactionType::WITHDRAWAL:
// three and five
if ('' === $data['destination_account_expense'] || null === $data['destination_account_expense']) {
// destination is a cash account.
return $accountRepository->getCashAccount();
}
$data = [
'name' => $data['destination_account_expense'],
'accountType' => 'expense',
'account_type_id' => null,
'virtualBalance' => 0,
'active' => true,
'iban' => null,
];
$destination = $accountRepository->store($data);
break;
case TransactionType::DEPOSIT . '-' . TransactionType::TRANSFER:
case TransactionType::TRANSFER . '-' . TransactionType::DEPOSIT:
// four and six
$destination = $destinationAccount;
break;
}
return $destination;
}
/**
* Get the source account.
*
* @param TransactionJournal $journal
* @param TransactionType $destinationType
* @param array $data
*
* @return Account
*
* @throws FireflyException
*
* @SuppressWarnings(PHPMD.ExcessiveMethodLength)
* @SuppressWarnings(PHPMD.CyclomaticComplexity)
*/
protected function getSourceAccount(TransactionJournal $journal, TransactionType $destinationType, array $data
): Account // helper for conversion. Get info from obj.
{
/** @var AccountRepositoryInterface $accountRepository */
$accountRepository = app(AccountRepositoryInterface::class);
$sourceAccount = $this->repository->getJournalSourceAccounts($journal)->first();
$destinationAccount = $this->repository->getJournalDestinationAccounts($journal)->first();
$sourceType = $journal->transactionType;
$joined = $sourceType->type . '-' . $destinationType->type;
switch ($joined) {
default:
throw new FireflyException('Cannot handle ' . $joined); // @codeCoverageIgnore
case TransactionType::WITHDRAWAL . '-' . TransactionType::DEPOSIT:
case TransactionType::TRANSFER . '-' . TransactionType::DEPOSIT:
if ('' === $data['source_account_revenue'] || null === $data['source_account_revenue']) {
// destination is a cash account.
return $accountRepository->getCashAccount();
}
$data = [
'name' => $data['source_account_revenue'],
'accountType' => 'revenue',
'virtualBalance' => 0,
'active' => true,
'account_type_id' => null,
'iban' => null,
];
$source = $accountRepository->store($data);
break;
case TransactionType::WITHDRAWAL . '-' . TransactionType::TRANSFER:
case TransactionType::TRANSFER . '-' . TransactionType::WITHDRAWAL:
$source = $sourceAccount;
break;
case TransactionType::DEPOSIT . '-' . TransactionType::WITHDRAWAL:
$source = $destinationAccount;
break;
case TransactionType::DEPOSIT . '-' . TransactionType::TRANSFER:
$source = $accountRepository->findNull((int)$data['source_account_asset']);
break;
}
return $source;
}
} }

View File

@@ -171,42 +171,4 @@ class SplitController extends Controller
return redirect($this->getPreviousUri('transactions.edit-split.uri')); return redirect($this->getPreviousUri('transactions.edit-split.uri'));
} }
/**
* Get info from old input.
*
* @param $array
* @param $old
*
* @return array
*
* @SuppressWarnings(PHPMD.ExcessiveMethodLength)
* @SuppressWarnings(PHPMD.CyclomaticComplexity)
*/
protected function updateWithPrevious($array, $old): array // update object with new info
{
if (0 === \count($old) || !isset($old['transactions'])) {
return $array;
}
$old = $old['transactions'];
foreach ($old as $index => $row) {
if (isset($array[$index])) {
/** @noinspection SlowArrayOperationsInLoopInspection */
$array[$index] = array_merge($array[$index], $row);
continue;
}
// take some info from first transaction, that should at least exist.
$array[$index] = $row;
$array[$index]['currency_id'] = $array[0]['currency_id'];
$array[$index]['currency_code'] = $array[0]['currency_code'] ?? '';
$array[$index]['currency_symbol'] = $array[0]['currency_symbol'] ?? '';
$array[$index]['foreign_amount'] = round($array[0]['foreign_destination_amount'] ?? '0', 12);
$array[$index]['foreign_currency_id'] = $array[0]['foreign_currency_id'];
$array[$index]['foreign_currency_code'] = $array[0]['foreign_currency_code'];
$array[$index]['foreign_currency_symbol'] = $array[0]['foreign_currency_symbol'];
}
return $array;
}
} }

View File

@@ -24,17 +24,22 @@ declare(strict_types=1);
namespace FireflyIII\Support\Http\Controllers; namespace FireflyIII\Support\Http\Controllers;
use Carbon\Carbon; use Carbon\Carbon;
use FireflyIII\Helpers\Collector\TransactionCollectorInterface;
use FireflyIII\Models\Account; use FireflyIII\Models\Account;
use FireflyIII\Models\AccountType; use FireflyIII\Models\AccountType;
use FireflyIII\Models\Bill;
use FireflyIII\Models\Budget; use FireflyIII\Models\Budget;
use FireflyIII\Models\BudgetLimit; use FireflyIII\Models\BudgetLimit;
use FireflyIII\Models\Tag; use FireflyIII\Models\Tag;
use FireflyIII\Models\Transaction; use FireflyIII\Models\Transaction;
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\Repositories\Category\CategoryRepositoryInterface; use FireflyIII\Repositories\Category\CategoryRepositoryInterface;
use FireflyIII\Support\CacheProperties; use FireflyIII\Support\CacheProperties;
use Illuminate\Support\Collection; use Illuminate\Support\Collection;
use Log;
use Throwable;
/** /**
* Trait AugumentData * Trait AugumentData
@@ -42,6 +47,8 @@ use Illuminate\Support\Collection;
*/ */
trait AugumentData trait AugumentData
{ {
/** /**
* Searches for the opposing account. * Searches for the opposing account.
* *
@@ -69,6 +76,116 @@ trait AugumentData
return $combined; return $combined;
} }
/**
* Group by category (earnings).
*
* @param Collection $assets
* @param Collection $opposing
* @param Carbon $start
* @param Carbon $end
*
* @return array
*
* @SuppressWarnings(PHPMD.CyclomaticComplexity)
* @SuppressWarnings(PHPMD.ExcessiveMethodLength)
*/
protected function earnedByCategory(Collection $assets, Collection $opposing, Carbon $start, Carbon $end): array // get data + augment with info
{
/** @var TransactionCollectorInterface $collector */
$collector = app(TransactionCollectorInterface::class);
$collector->setRange($start, $end)->setTypes([TransactionType::DEPOSIT])->setAccounts($assets);
$collector->setOpposingAccounts($opposing)->withCategoryInformation();
$set = $collector->getTransactions();
$sum = [];
// loop to support multi currency
foreach ($set as $transaction) {
$currencyId = $transaction->transaction_currency_id;
$categoryName = $transaction->transaction_category_name;
$categoryId = (int)$transaction->transaction_category_id;
// if null, grab from journal:
if (0 === $categoryId) {
$categoryName = $transaction->transaction_journal_category_name;
$categoryId = (int)$transaction->transaction_journal_category_id;
}
if (0 !== $categoryId) {
$categoryName = app('steam')->tryDecrypt($categoryName);
}
// if not set, set to zero:
if (!isset($sum[$categoryId][$currencyId])) {
$sum[$categoryId] = [
'grand_total' => '0',
'name' => $categoryName,
'per_currency' => [
$currencyId => [
'sum' => '0',
'category' => [
'id' => $categoryId,
'name' => $categoryName,
],
'currency' => [
'symbol' => $transaction->transaction_currency_symbol,
'dp' => $transaction->transaction_currency_dp,
],
],
],
];
}
// add amount
$sum[$categoryId]['per_currency'][$currencyId]['sum'] = bcadd(
$sum[$categoryId]['per_currency'][$currencyId]['sum'], $transaction->transaction_amount
);
$sum[$categoryId]['grand_total'] = bcadd($sum[$categoryId]['grand_total'], $transaction->transaction_amount);
}
return $sum;
}
/**
* Earned in period for accounts.
*
* @param Collection $assets
* @param Collection $opposing
* @param Carbon $start
* @param Carbon $end
*
* @return array
*/
protected function earnedInPeriod(Collection $assets, Collection $opposing, Carbon $start, Carbon $end): array // get data + augment with info
{
/** @var TransactionCollectorInterface $collector */
$collector = app(TransactionCollectorInterface::class);
$collector->setRange($start, $end)->setTypes([TransactionType::DEPOSIT])->setAccounts($assets);
$collector->setOpposingAccounts($opposing);
$set = $collector->getTransactions();
$sum = [
'grand_sum' => '0',
'per_currency' => [],
];
// loop to support multi currency
foreach ($set as $transaction) {
$currencyId = $transaction->transaction_currency_id;
// if not set, set to zero:
if (!isset($sum['per_currency'][$currencyId])) {
$sum['per_currency'][$currencyId] = [
'sum' => '0',
'currency' => [
'symbol' => $transaction->transaction_currency_symbol,
'dp' => $transaction->transaction_currency_dp,
],
];
}
// add amount
$sum['per_currency'][$currencyId]['sum'] = bcadd($sum['per_currency'][$currencyId]['sum'], $transaction->transaction_amount);
$sum['grand_sum'] = bcadd($sum['grand_sum'], $transaction->transaction_amount);
}
return $sum;
}
/** /**
* Small helper function for the revenue and expense account charts. * Small helper function for the revenue and expense account charts.
* *
@@ -180,6 +297,39 @@ trait AugumentData
return $return; return $return;
} }
/**
* Get the amount of money budgeted in a period.
*
* @param Budget $budget
* @param Carbon $start
* @param Carbon $end
*
* @return array
*/
protected function getBudgetedInPeriod(Budget $budget, Carbon $start, Carbon $end): array // get data + augment with info
{
/** @var BudgetRepositoryInterface $repository */
$repository = app(BudgetRepositoryInterface::class);
$key = app('navigation')->preferredCarbonFormat($start, $end);
$range = app('navigation')->preferredRangeFormat($start, $end);
$current = clone $start;
$budgeted = [];
while ($current < $end) {
/** @var Carbon $currentStart */
$currentStart = app('navigation')->startOfPeriod($current, $range);
/** @var Carbon $currentEnd */
$currentEnd = app('navigation')->endOfPeriod($current, $range);
$budgetLimits = $repository->getBudgetLimits($budget, $currentStart, $currentEnd);
$index = $currentStart->format($key);
$budgeted[$index] = $budgetLimits->sum('amount');
$currentEnd->addDay();
$current = clone $currentEnd;
}
return $budgeted;
}
/** /**
* Get the category names from a set of category ID's. Small helper function for some of the charts. * Get the category names from a set of category ID's. Small helper function for some of the charts.
* *
@@ -204,6 +354,46 @@ trait AugumentData
return $return; return $return;
} }
/**
* Get the expenses for a budget in a date range.
*
* @param Collection $limits
* @param Budget $budget
* @param Carbon $start
* @param Carbon $end
*
* @return array
*
* @SuppressWarnings(PHPMD.CyclomaticComplexity)
*/
protected function getExpensesForBudget(Collection $limits, Budget $budget, Carbon $start, Carbon $end): array // get data + augment with info
{
/** @var BudgetRepositoryInterface $repository */
$repository = app(BudgetRepositoryInterface::class);
$return = [];
if (0 === $limits->count()) {
$spent = $repository->spentInPeriod(new Collection([$budget]), new Collection, $start, $end);
if (0 !== bccomp($spent, '0')) {
$return[$budget->name]['spent'] = bcmul($spent, '-1');
$return[$budget->name]['left'] = 0;
$return[$budget->name]['overspent'] = 0;
}
return $return;
}
$rows = $this->spentInPeriodMulti($budget, $limits);
foreach ($rows as $name => $row) {
if (0 !== bccomp($row['spent'], '0') || 0 !== bccomp($row['left'], '0')) {
$return[$name] = $row;
}
}
unset($rows);
return $return;
}
/** /**
* Gets all budget limits for a budget. * Gets all budget limits for a budget.
* *
@@ -241,6 +431,7 @@ trait AugumentData
return $set; return $set;
} }
/** /**
* Helper function that groups expenses. * Helper function that groups expenses.
* *
@@ -333,4 +524,277 @@ trait AugumentData
return $grouped; return $grouped;
} }
/** @noinspection MoreThanThreeArgumentsInspection */
/**
* Spent by budget.
*
* @param Collection $assets
* @param Collection $opposing
* @param Carbon $start
* @param Carbon $end
*
* @return array
* @SuppressWarnings(PHPMD.CyclomaticComplexity)
* @SuppressWarnings(PHPMD.ExcessiveMethodLength)
*/
protected function spentByBudget(Collection $assets, Collection $opposing, Carbon $start, Carbon $end): array // get data + augment with info
{
/** @var TransactionCollectorInterface $collector */
$collector = app(TransactionCollectorInterface::class);
$collector->setRange($start, $end)->setTypes([TransactionType::WITHDRAWAL])->setAccounts($assets);
$collector->setOpposingAccounts($opposing)->withBudgetInformation();
$set = $collector->getTransactions();
$sum = [];
// loop to support multi currency
foreach ($set as $transaction) {
$currencyId = $transaction->transaction_currency_id;
$budgetName = $transaction->transaction_budget_name;
$budgetId = (int)$transaction->transaction_budget_id;
// if null, grab from journal:
if (0 === $budgetId) {
$budgetName = $transaction->transaction_journal_budget_name;
$budgetId = (int)$transaction->transaction_journal_budget_id;
}
if (0 !== $budgetId) {
$budgetName = app('steam')->tryDecrypt($budgetName);
}
// if not set, set to zero:
if (!isset($sum[$budgetId][$currencyId])) {
$sum[$budgetId] = [
'grand_total' => '0',
'name' => $budgetName,
'per_currency' => [
$currencyId => [
'sum' => '0',
'budget' => [
'id' => $budgetId,
'name' => $budgetName,
],
'currency' => [
'symbol' => $transaction->transaction_currency_symbol,
'dp' => $transaction->transaction_currency_dp,
],
],
],
];
}
// add amount
$sum[$budgetId]['per_currency'][$currencyId]['sum'] = bcadd(
$sum[$budgetId]['per_currency'][$currencyId]['sum'], $transaction->transaction_amount
);
$sum[$budgetId]['grand_total'] = bcadd($sum[$budgetId]['grand_total'], $transaction->transaction_amount);
}
return $sum;
}
/** @noinspection MoreThanThreeArgumentsInspection */
/**
* Spent by category.
*
* @param Collection $assets
* @param Collection $opposing
* @param Carbon $start
* @param Carbon $end
*
* @return array
*
* @SuppressWarnings(PHPMD.CyclomaticComplexity)
* @SuppressWarnings(PHPMD.ExcessiveMethodLength)
*/
protected function spentByCategory(Collection $assets, Collection $opposing, Carbon $start, Carbon $end): array // get data + augment with info
{
/** @var TransactionCollectorInterface $collector */
$collector = app(TransactionCollectorInterface::class);
$collector->setRange($start, $end)->setTypes([TransactionType::WITHDRAWAL])->setAccounts($assets);
$collector->setOpposingAccounts($opposing)->withCategoryInformation();
$set = $collector->getTransactions();
$sum = [];
// loop to support multi currency
foreach ($set as $transaction) {
$currencyId = $transaction->transaction_currency_id;
$categoryName = $transaction->transaction_category_name;
$categoryId = (int)$transaction->transaction_category_id;
// if null, grab from journal:
if (0 === $categoryId) {
$categoryName = $transaction->transaction_journal_category_name;
$categoryId = (int)$transaction->transaction_journal_category_id;
}
if (0 !== $categoryId) {
$categoryName = app('steam')->tryDecrypt($categoryName);
}
// if not set, set to zero:
if (!isset($sum[$categoryId][$currencyId])) {
$sum[$categoryId] = [
'grand_total' => '0',
'name' => $categoryName,
'per_currency' => [
$currencyId => [
'sum' => '0',
'category' => [
'id' => $categoryId,
'name' => $categoryName,
],
'currency' => [
'symbol' => $transaction->transaction_currency_symbol,
'dp' => $transaction->transaction_currency_dp,
],
],
],
];
}
// add amount
$sum[$categoryId]['per_currency'][$currencyId]['sum'] = bcadd(
$sum[$categoryId]['per_currency'][$currencyId]['sum'], $transaction->transaction_amount
);
$sum[$categoryId]['grand_total'] = bcadd($sum[$categoryId]['grand_total'], $transaction->transaction_amount);
}
return $sum;
}
/** @noinspection MoreThanThreeArgumentsInspection */
/**
* Spent in a period.
*
* @param Collection $assets
* @param Collection $opposing
* @param Carbon $start
* @param Carbon $end
*
* @return array
*/
protected function spentInPeriod(Collection $assets, Collection $opposing, Carbon $start, Carbon $end): array // get data + augment with info
{
/** @var TransactionCollectorInterface $collector */
$collector = app(TransactionCollectorInterface::class);
$collector->setRange($start, $end)->setTypes([TransactionType::WITHDRAWAL])->setAccounts($assets);
$collector->setOpposingAccounts($opposing);
$set = $collector->getTransactions();
$sum = [
'grand_sum' => '0',
'per_currency' => [],
];
// loop to support multi currency
foreach ($set as $transaction) {
$currencyId = (int)$transaction->transaction_currency_id;
// if not set, set to zero:
if (!isset($sum['per_currency'][$currencyId])) {
$sum['per_currency'][$currencyId] = [
'sum' => '0',
'currency' => [
'symbol' => $transaction->transaction_currency_symbol,
'dp' => $transaction->transaction_currency_dp,
],
];
}
// add amount
$sum['per_currency'][$currencyId]['sum'] = bcadd($sum['per_currency'][$currencyId]['sum'], $transaction->transaction_amount);
$sum['grand_sum'] = bcadd($sum['grand_sum'], $transaction->transaction_amount);
}
return $sum;
}
/** @noinspection MoreThanThreeArgumentsInspection */
/**
*
* Returns an array with the following values:
* 0 =>
* 'name' => name of budget + repetition
* 'left' => left in budget repetition (always zero)
* 'overspent' => spent more than budget repetition? (always zero)
* 'spent' => actually spent in period for budget
* 1 => (etc)
*
* @param Budget $budget
* @param Collection $limits
*
* @return array
*
* @SuppressWarnings(PHPMD.CyclomaticComplexity)
*
*/
protected function spentInPeriodMulti(Budget $budget, Collection $limits): array // get data + augment with info
{
/** @var BudgetRepositoryInterface $repository */
$repository = app(BudgetRepositoryInterface::class);
$return = [];
$format = (string)trans('config.month_and_day');
$name = $budget->name;
/** @var BudgetLimit $budgetLimit */
foreach ($limits as $budgetLimit) {
$expenses = $repository->spentInPeriod(new Collection([$budget]), new Collection, $budgetLimit->start_date, $budgetLimit->end_date);
$expenses = app('steam')->positive($expenses);
if ($limits->count() > 1) {
$name = $budget->name . ' ' . trans(
'firefly.between_dates',
[
'start' => $budgetLimit->start_date->formatLocalized($format),
'end' => $budgetLimit->end_date->formatLocalized($format),
]
);
}
$amount = $budgetLimit->amount;
$leftInLimit = bcsub($amount, $expenses);
$hasOverspent = bccomp($leftInLimit, '0') === -1;
$left = $hasOverspent ? '0' : bcsub($amount, $expenses);
$spent = $hasOverspent ? $amount : $expenses;
$overspent = $hasOverspent ? app('steam')->positive($leftInLimit) : '0';
$return[$name] = [
'left' => $left,
'overspent' => $overspent,
'spent' => $spent,
];
}
return $return;
}
/** @noinspection MoreThanThreeArgumentsInspection */
/**
* Returns an array with the following values:
* 'name' => "no budget" in local language
* 'repetition_left' => left in budget repetition (always zero)
* 'repetition_overspent' => spent more than budget repetition? (always zero)
* 'spent' => actually spent in period for budget.
*
* @param Carbon $start
* @param Carbon $end
*
* @return string
*/
protected function spentInPeriodWithout(Carbon $start, Carbon $end): string // get data + augment with info
{
// collector
/** @var TransactionCollectorInterface $collector */
$collector = app(TransactionCollectorInterface::class);
$types = [TransactionType::WITHDRAWAL];
$collector->setAllAssetAccounts()->setTypes($types)->setRange($start, $end)->withoutBudget();
$transactions = $collector->getTransactions();
$sum = '0';
/** @var Transaction $entry */
foreach ($transactions as $entry) {
$sum = bcadd($entry->transaction_amount, $sum);
}
return $sum;
}
} }

View File

@@ -90,4 +90,6 @@ trait BasicDataSupport
return $result; return $result;
} }
} }

View File

@@ -26,7 +26,11 @@ namespace FireflyIII\Support\Http\Controllers;
use Carbon\Carbon; use Carbon\Carbon;
use FireflyIII\Generator\Chart\Basic\GeneratorInterface; use FireflyIII\Generator\Chart\Basic\GeneratorInterface;
use FireflyIII\Models\Account; use FireflyIII\Models\Account;
use FireflyIII\Models\AccountType;
use FireflyIII\Models\Category;
use FireflyIII\Repositories\Account\AccountRepositoryInterface; use FireflyIII\Repositories\Account\AccountRepositoryInterface;
use FireflyIII\Repositories\Account\AccountTaskerInterface;
use FireflyIII\Repositories\Category\CategoryRepositoryInterface;
use FireflyIII\Repositories\Currency\CurrencyRepositoryInterface; use FireflyIII\Repositories\Currency\CurrencyRepositoryInterface;
use FireflyIII\Support\CacheProperties; use FireflyIII\Support\CacheProperties;
use Illuminate\Support\Collection; use Illuminate\Support\Collection;
@@ -103,4 +107,156 @@ trait ChartGeneration
return $data; return $data;
} }
/**
* Collects the incomes and expenses for the given periods, grouped per month. Will cache its results.
*
* @param Collection $accounts
* @param Carbon $start
* @param Carbon $end
*
* @return array
*
* @SuppressWarnings(PHPMD.ExcessiveMethodLength)
*/
protected function getChartData(Collection $accounts, Carbon $start, Carbon $end): array // chart helper function
{
$cache = new CacheProperties;
$cache->addProperty('chart.report.get-chart-data');
$cache->addProperty($start);
$cache->addProperty($accounts);
$cache->addProperty($end);
if ($cache->has()) {
return $cache->get(); // @codeCoverageIgnore
}
$currentStart = clone $start;
$spentArray = [];
$earnedArray = [];
/** @var AccountTaskerInterface $tasker */
$tasker = app(AccountTaskerInterface::class);
while ($currentStart <= $end) {
$currentEnd = app('navigation')->endOfPeriod($currentStart, '1M');
$earned = (string)array_sum(
array_map(
function ($item) {
return $item['sum'];
},
$tasker->getIncomeReport($currentStart, $currentEnd, $accounts)
)
);
$spent = (string)array_sum(
array_map(
function ($item) {
return $item['sum'];
},
$tasker->getExpenseReport($currentStart, $currentEnd, $accounts)
)
);
$label = $currentStart->format('Y-m') . '-01';
$spentArray[$label] = bcmul($spent, '-1');
$earnedArray[$label] = $earned;
$currentStart = app('navigation')->addPeriod($currentStart, '1M', 0);
}
$result = [
'spent' => $spentArray,
'earned' => $earnedArray,
];
$cache->store($result);
return $result;
}
/**
* Chart for a specific period (start and end).
*
*
* @param Category $category
* @param Carbon $start
* @param Carbon $end
*
* @return array
*
* @SuppressWarnings(PHPMD.ExcessiveMethodLength)
*/
protected function makePeriodChart(Category $category, Carbon $start, Carbon $end): array // chart helper method.
{
$cache = new CacheProperties;
$cache->addProperty($start);
$cache->addProperty($end);
$cache->addProperty($category->id);
$cache->addProperty('chart.category.period-chart');
if ($cache->has()) {
return $cache->get(); // @codeCoverageIgnore
}
/** @var AccountRepositoryInterface $accountRepository */
$accountRepository = app(AccountRepositoryInterface::class);
$accounts = $accountRepository->getAccountsByType([AccountType::DEFAULT, AccountType::ASSET]);
$repository = app(CategoryRepositoryInterface::class);
/** @var GeneratorInterface $generator */
$generator = app(GeneratorInterface::class);
// chart data
$chartData = [
[
'label' => (string)trans('firefly.spent'),
'entries' => [],
'type' => 'bar',
'backgroundColor' => 'rgba(219, 68, 55, 0.5)', // red
],
[
'label' => (string)trans('firefly.earned'),
'entries' => [],
'type' => 'bar',
'backgroundColor' => 'rgba(0, 141, 76, 0.5)', // green
],
[
'label' => (string)trans('firefly.sum'),
'entries' => [],
'type' => 'line',
'fill' => false,
],
];
$step = $this->calculateStep($start, $end);
while ($start <= $end) {
$spent = $repository->spentInPeriod(new Collection([$category]), $accounts, $start, $start);
$earned = $repository->earnedInPeriod(new Collection([$category]), $accounts, $start, $start);
$sum = bcadd($spent, $earned);
$label = trim(app('navigation')->periodShow($start, $step));
$chartData[0]['entries'][$label] = round(bcmul($spent, '-1'), 12);
$chartData[1]['entries'][$label] = round($earned, 12);
$chartData[2]['entries'][$label] = round($sum, 12);
switch ($step) {
default:
case '1D':
$start->addDay();
break;
case '1W':
$start->addDays(7);
break;
case '1M':
$start->addMonth();
break;
case '1Y':
$start->addYear();
break;
}
}
$data = $generator->multiSet($chartData);
$cache->store($data);
return $data;
}
} }

View File

@@ -23,9 +23,15 @@ declare(strict_types=1);
namespace FireflyIII\Support\Http\Controllers; namespace FireflyIII\Support\Http\Controllers;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Models\Account;
use FireflyIII\Models\Bill;
use FireflyIII\Models\TransactionJournal; use FireflyIII\Models\TransactionJournal;
use FireflyIII\Models\TransactionType; use FireflyIII\Models\TransactionType;
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
use FireflyIII\Repositories\Journal\JournalRepositoryInterface; use FireflyIII\Repositories\Journal\JournalRepositoryInterface;
use Log;
use Throwable;
/** /**
* Trait ModelInformation * Trait ModelInformation
@@ -33,6 +39,205 @@ use FireflyIII\Repositories\Journal\JournalRepositoryInterface;
*/ */
trait ModelInformation trait ModelInformation
{ {
/**
* Get actions based on a bill.
*
* @param Bill $bill
*
* @return array
*/
protected function getActionsForBill(Bill $bill): array // get info and augument
{
try {
$result = view(
'rules.partials.action',
[
'oldAction' => 'link_to_bill',
'oldValue' => $bill->name,
'oldChecked' => false,
'count' => 1,
]
)->render();
// @codeCoverageIgnoreStart
} catch (Throwable $e) {
Log::error(sprintf('Throwable was thrown in getActionsForBill(): %s', $e->getMessage()));
Log::error($e->getTraceAsString());
$result = 'Could not render view. See log files.';
}
// @codeCoverageIgnoreEnd
return [$result];
}
/**
* Get the destination account. Is complex.
*
* @param TransactionJournal $journal
* @param TransactionType $destinationType
* @param array $data
*
* @return Account
*
* @throws FireflyException
*
* @SuppressWarnings(PHPMD.ExcessiveMethodLength)
* @SuppressWarnings(PHPMD.CyclomaticComplexity)
*/
protected function getDestinationAccount(TransactionJournal $journal, TransactionType $destinationType, array $data
): Account // helper for conversion. Get info from obj.
{
/** @var AccountRepositoryInterface $accountRepository */
$accountRepository = app(AccountRepositoryInterface::class);
/** @var JournalRepositoryInterface $journalRepos */
$journalRepos = app(JournalRepositoryInterface::class);
$sourceAccount = $journalRepos->getJournalSourceAccounts($journal)->first();
$destinationAccount = $journalRepos->getJournalDestinationAccounts($journal)->first();
$sourceType = $journal->transactionType;
$joined = $sourceType->type . '-' . $destinationType->type;
switch ($joined) {
default:
throw new FireflyException('Cannot handle ' . $joined); // @codeCoverageIgnore
case TransactionType::WITHDRAWAL . '-' . TransactionType::DEPOSIT:
// one
$destination = $sourceAccount;
break;
case TransactionType::WITHDRAWAL . '-' . TransactionType::TRANSFER:
// two
$destination = $accountRepository->findNull((int)$data['destination_account_asset']);
break;
case TransactionType::DEPOSIT . '-' . TransactionType::WITHDRAWAL:
case TransactionType::TRANSFER . '-' . TransactionType::WITHDRAWAL:
// three and five
if ('' === $data['destination_account_expense'] || null === $data['destination_account_expense']) {
// destination is a cash account.
return $accountRepository->getCashAccount();
}
$data = [
'name' => $data['destination_account_expense'],
'accountType' => 'expense',
'account_type_id' => null,
'virtualBalance' => 0,
'active' => true,
'iban' => null,
];
$destination = $accountRepository->store($data);
break;
case TransactionType::DEPOSIT . '-' . TransactionType::TRANSFER:
case TransactionType::TRANSFER . '-' . TransactionType::DEPOSIT:
// four and six
$destination = $destinationAccount;
break;
}
return $destination;
}
/**
* Get the source account.
*
* @param TransactionJournal $journal
* @param TransactionType $destinationType
* @param array $data
*
* @return Account
*
* @throws FireflyException
*
* @SuppressWarnings(PHPMD.ExcessiveMethodLength)
* @SuppressWarnings(PHPMD.CyclomaticComplexity)
*/
protected function getSourceAccount(TransactionJournal $journal, TransactionType $destinationType, array $data
): Account // helper for conversion. Get info from obj.
{
/** @var AccountRepositoryInterface $accountRepository */
$accountRepository = app(AccountRepositoryInterface::class);
/** @var JournalRepositoryInterface $journalRepos */
$journalRepos = app(JournalRepositoryInterface::class);
$sourceAccount = $journalRepos->getJournalSourceAccounts($journal)->first();
$destinationAccount = $journalRepos->getJournalDestinationAccounts($journal)->first();
$sourceType = $journal->transactionType;
$joined = $sourceType->type . '-' . $destinationType->type;
switch ($joined) {
default:
throw new FireflyException('Cannot handle ' . $joined); // @codeCoverageIgnore
case TransactionType::WITHDRAWAL . '-' . TransactionType::DEPOSIT:
case TransactionType::TRANSFER . '-' . TransactionType::DEPOSIT:
if ('' === $data['source_account_revenue'] || null === $data['source_account_revenue']) {
// destination is a cash account.
return $accountRepository->getCashAccount();
}
$data = [
'name' => $data['source_account_revenue'],
'accountType' => 'revenue',
'virtualBalance' => 0,
'active' => true,
'account_type_id' => null,
'iban' => null,
];
$source = $accountRepository->store($data);
break;
case TransactionType::WITHDRAWAL . '-' . TransactionType::TRANSFER:
case TransactionType::TRANSFER . '-' . TransactionType::WITHDRAWAL:
$source = $sourceAccount;
break;
case TransactionType::DEPOSIT . '-' . TransactionType::WITHDRAWAL:
$source = $destinationAccount;
break;
case TransactionType::DEPOSIT . '-' . TransactionType::TRANSFER:
$source = $accountRepository->findNull((int)$data['source_account_asset']);
break;
}
return $source;
}
/**
* Create fake triggers to match the bill's properties
*
* @param Bill $bill
*
* @return array
*/
protected function getTriggersForBill(Bill $bill): array // get info and augument
{
$result = [];
$triggers = ['currency_is', 'amount_more', 'amount_less', 'description_contains'];
$values = [
$bill->transactionCurrency()->first()->name,
round((float)$bill->amount_min, 12),
round((float)$bill->amount_max, 12),
$bill->name,
];
foreach ($triggers as $index => $trigger) {
try {
$string = view(
'rules.partials.trigger',
[
'oldTrigger' => $trigger,
'oldValue' => $values[$index],
'oldChecked' => false,
'count' => $index + 1,
]
)->render();
// @codeCoverageIgnoreStart
} catch (Throwable $e) {
Log::debug(sprintf('Throwable was thrown in getTriggersForBill(): %s', $e->getMessage()));
Log::debug($e->getTraceAsString());
$string = '';
// @codeCoverageIgnoreEnd
}
if ('' !== $string) {
$result[] = $string;
}
}
return $result;
}
/** /**
* Is transaction opening balance? * Is transaction opening balance?
* *

View File

@@ -23,11 +23,13 @@ declare(strict_types=1);
namespace FireflyIII\Support\Http\Controllers; namespace FireflyIII\Support\Http\Controllers;
use FireflyIII\Helpers\Collection\BalanceLine; use FireflyIII\Helpers\Collection\BalanceLine;
use FireflyIII\Helpers\Report\PopupReportInterface; use FireflyIII\Helpers\Report\PopupReportInterface;
use FireflyIII\Models\AccountType; use FireflyIII\Models\AccountType;
use FireflyIII\Models\Budget; use FireflyIII\Models\Budget;
use FireflyIII\Models\Rule;
use FireflyIII\Models\RuleAction;
use FireflyIII\Models\RuleTrigger;
use FireflyIII\Models\Tag; use FireflyIII\Models\Tag;
use FireflyIII\Repositories\Account\AccountRepositoryInterface; use FireflyIII\Repositories\Account\AccountRepositoryInterface;
use FireflyIII\Repositories\Budget\BudgetRepositoryInterface; use FireflyIII\Repositories\Budget\BudgetRepositoryInterface;
@@ -43,6 +45,7 @@ use Throwable;
*/ */
trait RenderPartialViews trait RenderPartialViews
{ {
/** /**
* Get options for account report. * Get options for account report.
* *
@@ -103,7 +106,7 @@ trait RenderPartialViews
break; break;
case BalanceLine::ROLE_DEFAULTROLE === $role && null === $budget && null !== $account: case BalanceLine::ROLE_DEFAULTROLE === $role && null === $budget && null !== $account:
// normal row without a budget: // normal row without a budget:
$budget = new Budget; $budget = new Budget;
$journals = $popupHelper->balanceForNoBudget($account, $attributes); $journals = $popupHelper->balanceForNoBudget($account, $attributes);
$budget->name = (string)trans('firefly.no_budget'); $budget->name = (string)trans('firefly.no_budget');
break; break;
@@ -254,6 +257,85 @@ trait RenderPartialViews
return $view; return $view;
} }
/**
* Get current (from system) rule actions.
*
* @param Rule $rule
*
* @return array
*/
protected function getCurrentActions(Rule $rule): array // get info from object and present.
{
$index = 0;
$actions = [];
// todo must be repos
$currentActions = $rule->ruleActions()->orderBy('order', 'ASC')->get();
/** @var RuleAction $entry */
foreach ($currentActions as $entry) {
$count = ($index + 1);
try {
$actions[] = view(
'rules.partials.action',
[
'oldAction' => $entry->action_type,
'oldValue' => $entry->action_value,
'oldChecked' => $entry->stop_processing,
'count' => $count,
]
)->render();
// @codeCoverageIgnoreStart
} catch (Throwable $e) {
Log::debug(sprintf('Throwable was thrown in getCurrentActions(): %s', $e->getMessage()));
Log::error($e->getTraceAsString());
}
// @codeCoverageIgnoreEnd
++$index;
}
return $actions;
}
/**
* Get current (from DB) rule triggers.
*
* @param Rule $rule
*
* @return array
*
*/
protected function getCurrentTriggers(Rule $rule): array // get info from object and present.
{
$index = 0;
$triggers = [];
// todo must be repos
$currentTriggers = $rule->ruleTriggers()->orderBy('order', 'ASC')->get();
/** @var RuleTrigger $entry */
foreach ($currentTriggers as $entry) {
if ('user_action' !== $entry->trigger_type) {
$count = ($index + 1);
try {
$triggers[] = view(
'rules.partials.trigger',
[
'oldTrigger' => $entry->trigger_type,
'oldValue' => $entry->trigger_value,
'oldChecked' => $entry->stop_processing,
'count' => $count,
]
)->render();
// @codeCoverageIgnoreStart
} catch (Throwable $e) {
Log::debug(sprintf('Throwable was thrown in getCurrentTriggers(): %s', $e->getMessage()));
Log::error($e->getTraceAsString());
}
// @codeCoverageIgnoreEnd
++$index;
}
}
return $triggers;
}
/** /**
* Returns all the incomes that went to the given asset account. * Returns all the incomes that went to the given asset account.
* *

View File

@@ -54,6 +54,43 @@ use Symfony\Component\HttpFoundation\ParameterBag;
*/ */
trait RequestInformation trait RequestInformation
{ {
/**
* Get info from old input.
*
* @param $array
* @param $old
*
* @return array
*
* @SuppressWarnings(PHPMD.ExcessiveMethodLength)
* @SuppressWarnings(PHPMD.CyclomaticComplexity)
*/
protected function updateWithPrevious($array, $old): array // update object with new info
{
if (0 === \count($old) || !isset($old['transactions'])) {
return $array;
}
$old = $old['transactions'];
foreach ($old as $index => $row) {
if (isset($array[$index])) {
/** @noinspection SlowArrayOperationsInLoopInspection */
$array[$index] = array_merge($array[$index], $row);
continue;
}
// take some info from first transaction, that should at least exist.
$array[$index] = $row;
$array[$index]['currency_id'] = $array[0]['currency_id'];
$array[$index]['currency_code'] = $array[0]['currency_code'] ?? '';
$array[$index]['currency_symbol'] = $array[0]['currency_symbol'] ?? '';
$array[$index]['foreign_amount'] = round($array[0]['foreign_destination_amount'] ?? '0', 12);
$array[$index]['foreign_currency_id'] = $array[0]['foreign_currency_id'];
$array[$index]['foreign_currency_code'] = $array[0]['foreign_currency_code'];
$array[$index]['foreign_currency_symbol'] = $array[0]['foreign_currency_symbol'];
}
return $array;
}
/** /**
* Create data-array from a journal. * Create data-array from a journal.
@@ -66,8 +103,10 @@ trait RequestInformation
*/ */
protected function arrayFromJournal(Request $request, TransactionJournal $journal): array // convert user input. protected function arrayFromJournal(Request $request, TransactionJournal $journal): array // convert user input.
{ {
$sourceAccounts = $this->repository->getJournalSourceAccounts($journal); /** @var JournalRepositoryInterface $repository */
$destinationAccounts = $this->repository->getJournalDestinationAccounts($journal); $repository = app(JournalRepositoryInterface::class);
$sourceAccounts = $repository->getJournalSourceAccounts($journal);
$destinationAccounts = $repository->getJournalDestinationAccounts($journal);
$array = [ $array = [
'journal_description' => $request->old('journal_description', $journal->description), 'journal_description' => $request->old('journal_description', $journal->description),
'journal_amount' => '0', 'journal_amount' => '0',
@@ -82,14 +121,14 @@ trait RequestInformation
'tags' => implode(',', $journal->tags->pluck('tag')->toArray()), 'tags' => implode(',', $journal->tags->pluck('tag')->toArray()),
// all custom fields: // all custom fields:
'interest_date' => $request->old('interest_date', $this->repository->getMetaField($journal, 'interest_date')), 'interest_date' => $request->old('interest_date', $repository->getMetaField($journal, 'interest_date')),
'book_date' => $request->old('book_date', $this->repository->getMetaField($journal, 'book_date')), 'book_date' => $request->old('book_date', $repository->getMetaField($journal, 'book_date')),
'process_date' => $request->old('process_date', $this->repository->getMetaField($journal, 'process_date')), 'process_date' => $request->old('process_date', $repository->getMetaField($journal, 'process_date')),
'due_date' => $request->old('due_date', $this->repository->getMetaField($journal, 'due_date')), 'due_date' => $request->old('due_date', $repository->getMetaField($journal, 'due_date')),
'payment_date' => $request->old('payment_date', $this->repository->getMetaField($journal, 'payment_date')), 'payment_date' => $request->old('payment_date', $repository->getMetaField($journal, 'payment_date')),
'invoice_date' => $request->old('invoice_date', $this->repository->getMetaField($journal, 'invoice_date')), 'invoice_date' => $request->old('invoice_date', $repository->getMetaField($journal, 'invoice_date')),
'internal_reference' => $request->old('internal_reference', $this->repository->getMetaField($journal, 'internal_reference')), 'internal_reference' => $request->old('internal_reference', $repository->getMetaField($journal, 'internal_reference')),
'notes' => $request->old('notes', $this->repository->getNoteText($journal)), 'notes' => $request->old('notes', $repository->getNoteText($journal)),
// transactions. // transactions.
'transactions' => $this->getTransactionDataFromJournal($journal), 'transactions' => $this->getTransactionDataFromJournal($journal),