Replace transaction collector.

This commit is contained in:
James Cole
2019-05-29 18:28:28 +02:00
parent 627ef09f11
commit d13317095f
10 changed files with 648 additions and 220 deletions

View File

@@ -0,0 +1,381 @@
<?php
namespace FireflyIII\Console\Commands\Tools;
use Carbon\Carbon;
use FireflyIII\Console\Commands\VerifiesAccessToken;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Helpers\Collector\GroupCollectorInterface;
use FireflyIII\Models\AccountType;
use FireflyIII\Models\Rule;
use FireflyIII\Models\RuleGroup;
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
use FireflyIII\Repositories\Journal\JournalRepositoryInterface;
use FireflyIII\Repositories\Rule\RuleRepositoryInterface;
use FireflyIII\Repositories\RuleGroup\RuleGroupRepositoryInterface;
use FireflyIII\TransactionRules\Processor;
use Illuminate\Console\Command;
use Illuminate\Support\Collection;
use Log;
/**
* Class ApplyRules
*/
class ApplyRules extends Command
{
use VerifiesAccessToken;
/**
* The console command description.
*
* @var string
*/
protected $description = 'This command will apply your rules and rule groups on a selection of your transactions.';
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature
= 'firefly-iii:apply-rules
{--user=1 : The user ID that the import should import for.}
{--token= : The user\'s access token.}
{--accounts= : A comma-separated list of asset accounts or liabilities to apply your rules to.}
{--rule_groups= : A comma-separated list of rule groups to apply. Take the ID\'s of these rule groups from the Firefly III interface.}
{--rules= : A comma-separated list of rules to apply. Take the ID\'s of these rules from the Firefly III interface. Using this option overrules the option that selects rule groups.}
{--all_rules : If set, will overrule both settings and simply apply ALL of your rules.}
{--start_date= : The date of the earliest transaction to be included (inclusive). If omitted, will be your very first transaction ever. Format: YYYY-MM-DD}
{--end_date= : The date of the latest transaction to be included (inclusive). If omitted, will be your latest transaction ever. Format: YYYY-MM-DD}';
/** @var Collection */
private $accounts;
/** @var array */
private $acceptedAccounts;
/** @var Carbon */
private $endDate;
/** @var Collection */
private $results;
/** @var array */
private $ruleGroupSelection;
/** @var array */
private $ruleSelection;
/** @var Carbon */
private $startDate;
/** @var Collection */
private $groups;
/** @var bool */
private $allRules;
/** @var RuleRepositoryInterface */
private $ruleRepository;
/** @var RuleGroupRepositoryInterface */
private $ruleGroupRepository;
/**
* Create a new command instance.
*
* @return void
*/
public function __construct()
{
parent::__construct();
$this->allRules = false;
$this->accounts = new Collection;
$this->ruleSelection = [];
$this->ruleGroupSelection = [];
$this->results = new Collection;
$this->ruleRepository = app(RuleRepositoryInterface::class);
$this->ruleGroupRepository = app(RuleGroupRepositoryInterface::class);
$this->acceptedAccounts = [AccountType::DEFAULT, AccountType::DEBT, AccountType::ASSET, AccountType::LOAN, AccountType::MORTGAGE];
$this->groups = new Collection;
}
/**
* Execute the console command.
*
* @return int
* @throws FireflyException
*/
public function handle(): int
{
if (!$this->verifyAccessToken()) {
$this->error('Invalid access token.');
return 1;
}
// set user:
$this->ruleRepository->setUser($this->getUser());
$this->ruleGroupRepository->setUser($this->getUser());
$result = $this->verifyInput();
if (false === $result) {
return 1;
}
$this->allRules = $this->option('all_rules');
$this->grabAllRules();
// loop all groups and rules and indicate if they're included:
$count = 0;
/** @var RuleGroup $group */
foreach ($this->groups as $group) {
/** @var Rule $rule */
foreach ($group->rules as $rule) {
// if in rule selection, or group in selection or all rules, it's included.
if ($this->includeRule($rule, $group)) {
$count++;
}
}
}
if (0 === $count) {
$this->error('No rules or rule groups have been included.');
$this->warn('Make a selection using:');
$this->warn(' --rules=1,2,...');
$this->warn(' --rule_groups=1,2,...');
$this->warn(' --all_rules');
}
// get transactions from asset accounts.
/** @var GroupCollectorInterface $collector */
$collector = app(GroupCollectorInterface::class);
$collector->setUser($this->getUser());
$collector->setAccounts($this->accounts);
$collector->setRange($this->startDate, $this->endDate);
$journals = $collector->getExtractedJournals();
// start running rules.
$this->line(sprintf('Will apply %d rules to %d transactions.', $count, count($journals)));
// start looping.
$bar = $this->output->createProgressBar(count($journals) * $count);
Log::debug(sprintf('Now looping %d transactions.', count($journals)));
/** @var array $journal */
foreach ($journals as $journal) {
Log::debug('Start of new journal.');
foreach ($this->groups as $group) {
$groupTriggered = false;
/** @var Rule $rule */
foreach ($group->rules as $rule) {
$ruleTriggered = false;
// if in rule selection, or group in selection or all rules, it's included.
if ($this->includeRule($rule, $group)) {
/** @var Processor $processor */
$processor = app(Processor::class);
$processor->make($rule, true);
$ruleTriggered = $processor->handleJournalArray($journal);
$bar->advance();
if ($ruleTriggered) {
$groupTriggered = true;
}
}
// if the rule is triggered and stop processing is true, cancel the entire group.
if ($ruleTriggered && $rule->stop_processing) {
Log::info('Break out group because rule was triggered.');
break;
}
}
// if group is triggered and stop processing is true, cancel the whole thing.
if ($groupTriggered && $group->stop_processing) {
Log::info('Break out ALL because group was triggered.');
break;
}
}
Log::debug('Done with all rules for this group + done with journal.');
}
$this->line('');
$this->line('Done!');
return 0;
}
/**
* @return bool
* @throws FireflyException
*/
private function verifyInput(): bool
{
// verify account.
$result = $this->verifyInputAccounts();
if (false === $result) {
return $result;
}
// verify rule groups.
$result = $this->verifyInputRuleGroups();
if (false === $result) {
return $result;
}
// verify rules.
$result = $this->verifyInputRules();
if (false === $result) {
return $result;
}
$this->verifyInputDates();
return true;
}
/**
* @return bool
* @throws FireflyException
*/
private function verifyInputAccounts(): bool
{
$accountString = $this->option('accounts');
if (null === $accountString || '' === $accountString) {
$this->error('Please use the --accounts option to indicate the accounts to apply rules to.');
return false;
}
$finalList = new Collection;
$accountList = explode(',', $accountString);
if (0 === count($accountList)) {
$this->error('Please use the --accounts option to indicate the accounts to apply rules to.');
return false;
}
/** @var AccountRepositoryInterface $accountRepository */
$accountRepository = app(AccountRepositoryInterface::class);
$accountRepository->setUser($this->getUser());
foreach ($accountList as $accountId) {
$accountId = (int)$accountId;
$account = $accountRepository->findNull($accountId);
if (null !== $account && in_array($account->accountType->type, $this->acceptedAccounts, true)) {
$finalList->push($account);
}
}
if (0 === $finalList->count()) {
$this->error('Please make sure all accounts in --accounts are asset accounts or liabilities.');
return false;
}
$this->accounts = $finalList;
return true;
}
/**
* @return bool
*/
private function verifyInputRuleGroups(): bool
{
$ruleGroupString = $this->option('rule_groups');
if (null === $ruleGroupString || '' === $ruleGroupString) {
// can be empty.
return true;
}
$ruleGroupList = explode(',', $ruleGroupString);
if (0 === count($ruleGroupList)) {
// can be empty.
return true;
}
foreach ($ruleGroupList as $ruleGroupId) {
$ruleGroup = $this->ruleGroupRepository->find((int)$ruleGroupId);
if ($ruleGroup->active) {
$this->ruleGroupSelection[] = $ruleGroup->id;
}
if (false === $ruleGroup->active) {
$this->warn(sprintf('Will ignore inactive rule group #%d ("%s")', $ruleGroup->id, $ruleGroup->title));
}
}
return true;
}
/**
* @return bool
*/
private function verifyInputRules(): bool
{
$ruleString = $this->option('rules');
if (null === $ruleString || '' === $ruleString) {
// can be empty.
return true;
}
$ruleList = explode(',', $ruleString);
if (0 === count($ruleList)) {
// can be empty.
return true;
}
foreach ($ruleList as $ruleId) {
$rule = $this->ruleRepository->find((int)$ruleId);
if (null !== $rule && $rule->active) {
$this->ruleSelection[] = $rule->id;
}
}
return true;
}
/**
* @throws FireflyException
*/
private function verifyInputDates(): void
{
// parse start date.
$startDate = Carbon::now()->startOfMonth();
$startString = $this->option('start_date');
if (null === $startString) {
/** @var JournalRepositoryInterface $repository */
$repository = app(JournalRepositoryInterface::class);
$repository->setUser($this->getUser());
$first = $repository->firstNull();
if (null !== $first) {
$startDate = $first->date;
}
}
if (null !== $startString && '' !== $startString) {
$startDate = Carbon::createFromFormat('Y-m-d', $startString);
}
// parse end date
$endDate = Carbon::now();
$endString = $this->option('end_date');
if (null !== $endString && '' !== $endString) {
$endDate = Carbon::createFromFormat('Y-m-d', $endString);
}
if ($startDate > $endDate) {
[$endDate, $startDate] = [$startDate, $endDate];
}
$this->startDate = $startDate;
$this->endDate = $endDate;
}
/**
*/
private function grabAllRules(): void
{
$this->groups = $this->ruleGroupRepository->getActiveGroups();
}
/**
* @param Rule $rule
* @param RuleGroup $group
* @return bool
*/
private function includeRule(Rule $rule, RuleGroup $group): bool
{
return in_array($group->id, $this->ruleGroupSelection, true) ||
in_array($rule->id, $this->ruleSelection, true) ||
$this->allRules;
}
}

View File

@@ -51,7 +51,7 @@ class StoredGroupEventHandler
foreach ($journals as $journal) { foreach ($journals as $journal) {
$ruleGroupRepos->setUser($journal->user); $ruleGroupRepos->setUser($journal->user);
$groups = $ruleGroupRepos->getActiveGroups($journal->user); $groups = $ruleGroupRepos->getActiveGroups();
/** @var RuleGroup $group */ /** @var RuleGroup $group */
foreach ($groups as $group) { foreach ($groups as $group) {

View File

@@ -52,7 +52,7 @@ class UpdatedGroupEventHandler
foreach ($journals as $journal) { foreach ($journals as $journal) {
$ruleGroupRepos->setUser($journal->user); $ruleGroupRepos->setUser($journal->user);
$groups = $ruleGroupRepos->getActiveGroups($journal->user); $groups = $ruleGroupRepos->getActiveGroups();
/** @var RuleGroup $group */ /** @var RuleGroup $group */
foreach ($groups as $group) { foreach ($groups as $group) {

View File

@@ -25,6 +25,7 @@ namespace FireflyIII\Http\Controllers\Account;
use Carbon\Carbon; use Carbon\Carbon;
use FireflyIII\Exceptions\FireflyException; use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Helpers\Collector\GroupCollectorInterface;
use FireflyIII\Helpers\Collector\TransactionCollectorInterface; use FireflyIII\Helpers\Collector\TransactionCollectorInterface;
use FireflyIII\Http\Controllers\Controller; use FireflyIII\Http\Controllers\Controller;
use FireflyIII\Models\Account; use FireflyIII\Models\Account;
@@ -76,8 +77,8 @@ class ShowController extends Controller
/** /**
* Show an account. * Show an account.
* *
* @param Request $request * @param Request $request
* @param Account $account * @param Account $account
* @param Carbon|null $start * @param Carbon|null $start
* @param Carbon|null $end * @param Carbon|null $end
* *
@@ -119,19 +120,22 @@ class ShowController extends Controller
$subTitle = (string)trans('firefly.journals_in_period_for_account', ['name' => $account->name, 'start' => $fStart, 'end' => $fEnd]); $subTitle = (string)trans('firefly.journals_in_period_for_account', ['name' => $account->name, 'start' => $fStart, 'end' => $fEnd]);
$chartUri = route('chart.account.period', [$account->id, $start->format('Y-m-d'), $end->format('Y-m-d')]); $chartUri = route('chart.account.period', [$account->id, $start->format('Y-m-d'), $end->format('Y-m-d')]);
$periods = $this->getAccountPeriodOverview($account, $end); $periods = $this->getAccountPeriodOverview($account, $end);
/** @var TransactionCollectorInterface $collector */
$collector = app(TransactionCollectorInterface::class);
$collector->setAccounts(new Collection([$account]))->setLimit($pageSize)->setPage($page);
$collector->setRange($start, $end);
$transactions = $collector->getPaginatedTransactions();
$transactions->setPath(route('accounts.show', [$account->id, $start->format('Y-m-d'), $end->format('Y-m-d')]));
$showAll = false;
/** @var GroupCollectorInterface $collector */
$collector = app(GroupCollectorInterface::class);
$collector
->setAccounts(new Collection([$account]))
->setLimit($pageSize)
->setPage($page)
->setRange($start, $end);
$groups = $collector->getPaginatedGroups();
$groups->setPath(route('accounts.show', [$account->id, $start->format('Y-m-d'), $end->format('Y-m-d')]));
$showAll = false;
return view( return view(
'accounts.show', 'accounts.show',
compact( compact(
'account', 'showAll', 'what', 'currency', 'today', 'periods', 'subTitleIcon', 'transactions', 'subTitle', 'start', 'end', 'account', 'showAll', 'what', 'currency', 'today', 'periods', 'subTitleIcon', 'groups', 'subTitle', 'start', 'end',
'chartUri' 'chartUri'
) )
); );

View File

@@ -34,19 +34,18 @@ use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
/** /**
* Class RuleGroup. * Class RuleGroup.
* *
* @property bool $active * @property bool $active
* @property User $user * @property User $user
* @property Carbon $created_at * @property Carbon $created_at
* @property Carbon $updated_at * @property Carbon $updated_at
* @property string $title * @property string $title
* @property string $text * @property string $text
* @property int $id * @property int $id
* @property int $order * @property int $order
* @property Collection $rules * @property Collection $rules
* @property string description * @property string description
* @property \Illuminate\Support\Carbon|null $deleted_at * @property \Illuminate\Support\Carbon|null $deleted_at
* @property int $user_id * @property int $user_id
* @property string|null $description
* @method static bool|null forceDelete() * @method static bool|null forceDelete()
* @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\RuleGroup newModelQuery() * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\RuleGroup newModelQuery()
* @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\RuleGroup newQuery() * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\RuleGroup newQuery()
@@ -64,6 +63,7 @@ use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
* @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\RuleGroup whereUserId($value) * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\RuleGroup whereUserId($value)
* @method static \Illuminate\Database\Query\Builder|\FireflyIII\Models\RuleGroup withTrashed() * @method static \Illuminate\Database\Query\Builder|\FireflyIII\Models\RuleGroup withTrashed()
* @method static \Illuminate\Database\Query\Builder|\FireflyIII\Models\RuleGroup withoutTrashed() * @method static \Illuminate\Database\Query\Builder|\FireflyIII\Models\RuleGroup withoutTrashed()
* @property bool $stop_processing
* @mixin \Eloquent * @mixin \Eloquent
*/ */
class RuleGroup extends Model class RuleGroup extends Model
@@ -76,15 +76,16 @@ class RuleGroup extends Model
*/ */
protected $casts protected $casts
= [ = [
'created_at' => 'datetime', 'created_at' => 'datetime',
'updated_at' => 'datetime', 'updated_at' => 'datetime',
'deleted_at' => 'datetime', 'deleted_at' => 'datetime',
'active' => 'boolean', 'active' => 'boolean',
'order' => 'int', 'stop_processing' => 'boolean',
'order' => 'int',
]; ];
/** @var array Fields that can be filled */ /** @var array Fields that can be filled */
protected $fillable = ['user_id', 'order', 'title', 'description', 'active']; protected $fillable = ['user_id', 'stop_processing', 'order', 'title', 'description', 'active'];
/** /**
* Route binder. Converts the key in the URL to the specified object (or throw 404). * Route binder. Converts the key in the URL to the specified object (or throw 404).

View File

@@ -56,7 +56,7 @@ class RuleGroupRepository implements RuleGroupRepositoryInterface
} }
/** /**
* @param RuleGroup $ruleGroup * @param RuleGroup $ruleGroup
* @param RuleGroup|null $moveTo * @param RuleGroup|null $moveTo
* *
* @return bool * @return bool
@@ -85,6 +85,49 @@ class RuleGroupRepository implements RuleGroupRepositoryInterface
return true; return true;
} }
/**
* @return bool
*/
public function resetRuleGroupOrder(): bool
{
$this->user->ruleGroups()->whereNotNull('deleted_at')->update(['order' => 0]);
$set = $this->user->ruleGroups()->where('active', 1)->orderBy('order', 'ASC')->get();
$count = 1;
/** @var RuleGroup $entry */
foreach ($set as $entry) {
$entry->order = $count;
$entry->save();
++$count;
}
return true;
}
/**
* @param RuleGroup $ruleGroup
*
* @return bool
*/
public function resetRulesInGroupOrder(RuleGroup $ruleGroup): bool
{
$ruleGroup->rules()->whereNotNull('deleted_at')->update(['order' => 0]);
$set = $ruleGroup->rules()
->orderBy('order', 'ASC')
->orderBy('updated_at', 'DESC')
->get();
$count = 1;
/** @var Rule $entry */
foreach ($set as $entry) {
$entry->order = $count;
$entry->save();
++$count;
}
return true;
}
/** /**
* @param int $ruleGroupId * @param int $ruleGroupId
* *
@@ -109,13 +152,11 @@ class RuleGroupRepository implements RuleGroupRepositoryInterface
} }
/** /**
* @param User $user
*
* @return Collection * @return Collection
*/ */
public function getActiveGroups(User $user): Collection public function getActiveGroups(): Collection
{ {
return $user->ruleGroups()->where('rule_groups.active', 1)->orderBy('order', 'ASC')->get(['rule_groups.*']); return $this->user->ruleGroups()->where('rule_groups.active', 1)->orderBy('order', 'ASC')->get(['rule_groups.*']);
} }
/** /**
@@ -160,16 +201,6 @@ class RuleGroupRepository implements RuleGroupRepositoryInterface
->get(['rules.*']); ->get(['rules.*']);
} }
/**
* @return int
*/
public function getHighestOrderRuleGroup(): int
{
$entry = $this->user->ruleGroups()->max('order');
return (int)$entry;
}
/** /**
* @param User $user * @param User $user
* *
@@ -253,49 +284,6 @@ class RuleGroupRepository implements RuleGroupRepositoryInterface
return true; return true;
} }
/**
* @return bool
*/
public function resetRuleGroupOrder(): bool
{
$this->user->ruleGroups()->whereNotNull('deleted_at')->update(['order' => 0]);
$set = $this->user->ruleGroups()->where('active', 1)->orderBy('order', 'ASC')->get();
$count = 1;
/** @var RuleGroup $entry */
foreach ($set as $entry) {
$entry->order = $count;
$entry->save();
++$count;
}
return true;
}
/**
* @param RuleGroup $ruleGroup
*
* @return bool
*/
public function resetRulesInGroupOrder(RuleGroup $ruleGroup): bool
{
$ruleGroup->rules()->whereNotNull('deleted_at')->update(['order' => 0]);
$set = $ruleGroup->rules()
->orderBy('order', 'ASC')
->orderBy('updated_at', 'DESC')
->get();
$count = 1;
/** @var Rule $entry */
foreach ($set as $entry) {
$entry->order = $count;
$entry->save();
++$count;
}
return true;
}
/** /**
* @param User $user * @param User $user
*/ */
@@ -328,9 +316,19 @@ class RuleGroupRepository implements RuleGroupRepositoryInterface
return $newRuleGroup; return $newRuleGroup;
} }
/**
* @return int
*/
public function getHighestOrderRuleGroup(): int
{
$entry = $this->user->ruleGroups()->max('order');
return (int)$entry;
}
/** /**
* @param RuleGroup $ruleGroup * @param RuleGroup $ruleGroup
* @param array $data * @param array $data
* *
* @return RuleGroup * @return RuleGroup
*/ */

View File

@@ -59,11 +59,9 @@ interface RuleGroupRepositoryInterface
public function get(): Collection; public function get(): Collection;
/** /**
* @param User $user
*
* @return Collection * @return Collection
*/ */
public function getActiveGroups(User $user): Collection; public function getActiveGroups(): Collection;
/** /**
* @param RuleGroup $group * @param RuleGroup $group

View File

@@ -25,6 +25,7 @@ namespace FireflyIII\Support\Http\Controllers;
use Carbon\Carbon; use Carbon\Carbon;
use FireflyIII\Exceptions\FireflyException; use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Helpers\Collector\GroupCollectorInterface;
use FireflyIII\Helpers\Collector\GroupSumCollectorInterface; use FireflyIII\Helpers\Collector\GroupSumCollectorInterface;
use FireflyIII\Helpers\Collector\TransactionCollectorInterface; use FireflyIII\Helpers\Collector\TransactionCollectorInterface;
use FireflyIII\Helpers\Filter\InternalTransferFilter; use FireflyIII\Helpers\Filter\InternalTransferFilter;
@@ -69,13 +70,12 @@ trait PeriodOverview
* The method has been refactored recently for better performance. * The method has been refactored recently for better performance.
* *
* @param Account $account The account involved * @param Account $account The account involved
* @param Carbon $date The start date. * @param Carbon $date The start date.
* *
* @return Collection * @return Collection
*/ */
protected function getAccountPeriodOverview(Account $account, Carbon $date): Collection protected function getAccountPeriodOverview(Account $account, Carbon $date): Collection
{ {
throw new FireflyException('Is using collector.');
/** @var AccountRepositoryInterface $repository */ /** @var AccountRepositoryInterface $repository */
$repository = app(AccountRepositoryInterface::class); $repository = app(AccountRepositoryInterface::class);
$range = app('preferences')->get('viewRange', '1M')->data; $range = app('preferences')->get('viewRange', '1M')->data;
@@ -100,25 +100,30 @@ trait PeriodOverview
$entries = new Collection; $entries = new Collection;
// loop dates // loop dates
foreach ($dates as $currentDate) { foreach ($dates as $currentDate) {
/** @var TransactionCollectorInterface $collector */
$collector = app(TransactionCollectorInterface::class);
$collector->setAccounts(new Collection([$account]))->setRange($currentDate['start'], $currentDate['end'])->setTypes([TransactionType::DEPOSIT])
->withOpposingAccount();
$earnedSet = $collector->getTransactions();
$earned = $this->groupByCurrency($earnedSet);
/** @var TransactionCollectorInterface $collector */ // collect from start to end:
$collector = app(TransactionCollectorInterface::class); /** @var GroupCollectorInterface $collector */
$collector->setAccounts(new Collection([$account]))->setRange($currentDate['start'], $currentDate['end'])->setTypes([TransactionType::WITHDRAWAL]) $collector = app(GroupCollectorInterface::class);
->withOpposingAccount(); $collector->setAccounts(new Collection([$account]));
$spentSet = $collector->getTransactions(); $collector->setRange($currentDate['start'], $currentDate['end']);
$collector->setTypes([TransactionType::DEPOSIT]);
$earnedSet = $collector->getExtractedJournals();
$earned = $this->groupByCurrency($earnedSet);
/** @var GroupCollectorInterface $collector */
$collector = app(GroupCollectorInterface::class);
$collector->setAccounts(new Collection([$account]));
$collector->setRange($currentDate['start'], $currentDate['end']);
$collector->setTypes([TransactionType::WITHDRAWAL]);
$spentSet = $collector->getExtractedJournals();
$spent = $this->groupByCurrency($spentSet); $spent = $this->groupByCurrency($spentSet);
$title = app('navigation')->periodShow($currentDate['start'], $currentDate['period']); $title = app('navigation')->periodShow($currentDate['start'], $currentDate['period']);
/** @noinspection PhpUndefinedMethodInspection */ /** @noinspection PhpUndefinedMethodInspection */
$entries->push( $entries->push(
[ [
'transactions' => 0, 'transactions' => count($spentSet) + count($earnedSet),
'title' => $title, 'title' => $title,
'spent' => $spent, 'spent' => $spent,
'earned' => $earned, 'earned' => $earned,
@@ -127,17 +132,44 @@ trait PeriodOverview
] ]
); );
} }
//$cache->store($entries);
$cache->store($entries);
return $entries; return $entries;
} }
/**
* @param array $journals
*
* @return array
*/
private function groupByCurrency(array $journals): array
{
$return = [];
/** @var array $journal */
foreach ($journals as $journal) {
$currencyId = (int)$journal['currency_id'];
if (!isset($return[$currencyId])) {
$currency = new TransactionCurrency;
$currency->symbol = $journal['currency_symbol'];
$currency->decimal_places = $journal['currency_decimal_places'];
$currency->name = $journal['currency_name'];
$return[$currencyId] = [
'amount' => '0',
'currency' => $currency,
//'currency' => 'x',//$currency,
];
}
$return[$currencyId]['amount'] = bcadd($return[$currencyId]['amount'], $journal['amount']);
}
return $return;
}
/** /**
* Overview for single category. Has been refactored recently. * Overview for single category. Has been refactored recently.
* *
* @param Category $category * @param Category $category
* @param Carbon $date * @param Carbon $date
* *
* @return Collection * @return Collection
*/ */
@@ -357,7 +389,7 @@ trait PeriodOverview
/** /**
* This shows a period overview for a tag. It goes back in time and lists all relevant transactions and sums. * This shows a period overview for a tag. It goes back in time and lists all relevant transactions and sums.
* *
* @param Tag $tag * @param Tag $tag
* *
* @param Carbon $date * @param Carbon $date
* *
@@ -524,31 +556,4 @@ trait PeriodOverview
return $return; return $return;
} }
/**
* @param array $journals
*
* @return array
*/
private function groupByCurrency(array $journals): array
{
$return = [];
/** @var array $journal */
foreach ($journals as $journal) {
$currencyId = (int)$journal['currency_id'];
if (!isset($return[$currencyId])) {
$currency = new TransactionCurrency;
$currency->symbol = $journal['currency_symbol'];
$currency->decimal_places = $journal['currency_decimal_places'];
$currency->name = $journal['currency_name'];
$return[$currencyId] = [
'amount' => '0',
'currency' => $currency,
];
}
$return[$currencyId]['amount'] = bcadd($return[$currencyId]['amount'], $journal['amount']);
}
return $return;
}
} }

View File

@@ -62,26 +62,6 @@ class Processor
$this->actions = new Collection; $this->actions = new Collection;
} }
/**
* Return found triggers
*
* @return int
*/
public function getFoundTriggers(): int
{
return $this->foundTriggers;
}
/**
* Set found triggers
*
* @param int $foundTriggers
*/
public function setFoundTriggers(int $foundTriggers): void
{
$this->foundTriggers = $foundTriggers;
}
/** /**
* Returns the rule * Returns the rule
* *
@@ -127,6 +107,126 @@ class Processor
return false; return false;
} }
/**
* Method to check whether the current transaction would be triggered
* by the given list of triggers.
*
* @return bool
*/
private function triggered(): bool
{
Log::debug('start of Processor::triggered()');
$foundTriggers = $this->getFoundTriggers();
$hitTriggers = 0;
Log::debug(sprintf('Found triggers starts at %d', $foundTriggers));
/** @var AbstractTrigger $trigger */
foreach ($this->triggers as $trigger) {
++$foundTriggers;
Log::debug(sprintf('Now checking trigger %s with value %s', \get_class($trigger), $trigger->getTriggerValue()));
/** @var AbstractTrigger $trigger */
if ($trigger->triggered($this->journal)) {
Log::debug('Is a match!');
++$hitTriggers;
// is non-strict? then return true!
if (!$this->strict && UserAction::class !== \get_class($trigger)) {
Log::debug('Rule is set as non-strict, return true!');
return true;
}
if (!$this->strict && UserAction::class === \get_class($trigger)) {
Log::debug('Rule is set as non-strict, but action was "user-action". Will not return true.');
}
}
if ($trigger->stopProcessing) {
Log::debug('Stop processing this trigger and break.');
break;
}
}
$result = ($hitTriggers === $foundTriggers && $foundTriggers > 0);
Log::debug('Result of triggered()', ['hitTriggers' => $hitTriggers, 'foundTriggers' => $foundTriggers, 'result' => $result]);
return $result;
}
/**
* Return found triggers
*
* @return int
*/
public function getFoundTriggers(): int
{
return $this->foundTriggers;
}
/**
* Set found triggers
*
* @param int $foundTriggers
*/
public function setFoundTriggers(int $foundTriggers): void
{
$this->foundTriggers = $foundTriggers;
}
/**
* Run the actions
*
* @return void
* @throws \FireflyIII\Exceptions\FireflyException
*/
private function actions(): void
{
/**
* @var int
* @var RuleAction $action
*/
foreach ($this->actions as $action) {
/** @var ActionInterface $actionClass */
$actionClass = ActionFactory::getAction($action);
Log::debug(sprintf('Fire action %s on journal #%d', \get_class($actionClass), $this->journal->id));
$actionClass->act($this->journal);
if ($action->stop_processing) {
Log::debug('Stop processing now and break.');
break;
}
}
}
/**
* This method will scan the given transaction journal and check if it matches the triggers found in the Processor
* If so, it will also attempt to run the given actions on the journal. It returns a bool indicating if the transaction journal
* matches all of the triggers (regardless of whether the Processor could act on it).
*
* @param array $journal
*
* @return bool
* @throws \FireflyIII\Exceptions\FireflyException
*/
public function handleJournalArray(array $journal): bool
{
Log::debug(sprintf('handleJournalArray for journal #%d (group #%d)', $journal['transaction_journal_id'], $journal['transaction_group_id']));
// grab the actual journal.
$this->journal = TransactionJournal::find($journal['transaction_journal_id']);
// get all triggers:
$triggered = $this->triggered();
if ($triggered) {
Log::debug('Rule is triggered, go to actions.');
if ($this->actions->count() > 0) {
Log::debug('Has more than zero actions.');
$this->actions();
}
if (0 === $this->actions->count()) {
Log::info('Rule has no actions!');
}
return true;
}
return false;
}
/** /**
* This method will scan the given transaction journal and check if it matches the triggers found in the Processor * This method will scan the given transaction journal and check if it matches the triggers found in the Processor
* If so, it will also attempt to run the given actions on the journal. It returns a bool indicating if the transaction journal * If so, it will also attempt to run the given actions on the journal. It returns a bool indicating if the transaction journal
@@ -236,69 +336,4 @@ class Processor
} }
} }
/**
* Run the actions
*
* @return void
* @throws \FireflyIII\Exceptions\FireflyException
*/
private function actions(): void
{
/**
* @var int
* @var RuleAction $action
*/
foreach ($this->actions as $action) {
/** @var ActionInterface $actionClass */
$actionClass = ActionFactory::getAction($action);
Log::debug(sprintf('Fire action %s on journal #%d', \get_class($actionClass), $this->journal->id));
$actionClass->act($this->journal);
if ($action->stop_processing) {
Log::debug('Stop processing now and break.');
break;
}
}
}
/**
* Method to check whether the current transaction would be triggered
* by the given list of triggers.
*
* @return bool
*/
private function triggered(): bool
{
Log::debug('start of Processor::triggered()');
$foundTriggers = $this->getFoundTriggers();
$hitTriggers = 0;
Log::debug(sprintf('Found triggers starts at %d', $foundTriggers));
/** @var AbstractTrigger $trigger */
foreach ($this->triggers as $trigger) {
++$foundTriggers;
Log::debug(sprintf('Now checking trigger %s with value %s', \get_class($trigger), $trigger->getTriggerValue()));
/** @var AbstractTrigger $trigger */
if ($trigger->triggered($this->journal)) {
Log::debug('Is a match!');
++$hitTriggers;
// is non-strict? then return true!
if (!$this->strict && UserAction::class !== \get_class($trigger)) {
Log::debug('Rule is set as non-strict, return true!');
return true;
}
if (!$this->strict && UserAction::class === \get_class($trigger)) {
Log::debug('Rule is set as non-strict, but action was "user-action". Will not return true.');
}
}
if ($trigger->stopProcessing) {
Log::debug('Stop processing this trigger and break.');
break;
}
}
$result = ($hitTriggers === $foundTriggers && $foundTriggers > 0);
Log::debug('Result of triggered()', ['hitTriggers' => $hitTriggers, 'foundTriggers' => $foundTriggers, 'result' => $result]);
return $result;
}
} }

View File

@@ -26,6 +26,9 @@ class ChangesForV480 extends Migration
$table->dropColumn('transaction_group_id'); $table->dropColumn('transaction_group_id');
} }
); );
Schema::table('rule_groups', function (Blueprint $table) {
$table->dropColumn('stop_processing');
});
} }
/** /**
@@ -50,5 +53,8 @@ class ChangesForV480 extends Migration
$table->foreign('transaction_group_id')->references('id')->on('transaction_groups')->onDelete('cascade'); $table->foreign('transaction_group_id')->references('id')->on('transaction_groups')->onDelete('cascade');
} }
); );
Schema::table('rule_groups', function (Blueprint $table) {
$table->boolean('stop_processing')->default(false);
});
} }
} }