From d13317095f7db833b1f23f2405ca9d216081fb52 Mon Sep 17 00:00:00 2001 From: James Cole Date: Wed, 29 May 2019 18:28:28 +0200 Subject: [PATCH] Replace transaction collector. --- app/Console/Commands/Tools/ApplyRules.php | 381 ++++++++++++++++++ .../Events/StoredGroupEventHandler.php | 2 +- .../Events/UpdatedGroupEventHandler.php | 2 +- .../Controllers/Account/ShowController.php | 24 +- app/Models/RuleGroup.php | 33 +- .../RuleGroup/RuleGroupRepository.php | 116 +++--- .../RuleGroupRepositoryInterface.php | 4 +- .../Http/Controllers/PeriodOverview.php | 95 ++--- app/TransactionRules/Processor.php | 205 ++++++---- .../2019_03_22_183214_changes_for_v480.php | 6 + 10 files changed, 648 insertions(+), 220 deletions(-) create mode 100644 app/Console/Commands/Tools/ApplyRules.php diff --git a/app/Console/Commands/Tools/ApplyRules.php b/app/Console/Commands/Tools/ApplyRules.php new file mode 100644 index 0000000000..3b3fb2e87f --- /dev/null +++ b/app/Console/Commands/Tools/ApplyRules.php @@ -0,0 +1,381 @@ +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; + } +} diff --git a/app/Handlers/Events/StoredGroupEventHandler.php b/app/Handlers/Events/StoredGroupEventHandler.php index d4c6223587..ea56b12126 100644 --- a/app/Handlers/Events/StoredGroupEventHandler.php +++ b/app/Handlers/Events/StoredGroupEventHandler.php @@ -51,7 +51,7 @@ class StoredGroupEventHandler foreach ($journals as $journal) { $ruleGroupRepos->setUser($journal->user); - $groups = $ruleGroupRepos->getActiveGroups($journal->user); + $groups = $ruleGroupRepos->getActiveGroups(); /** @var RuleGroup $group */ foreach ($groups as $group) { diff --git a/app/Handlers/Events/UpdatedGroupEventHandler.php b/app/Handlers/Events/UpdatedGroupEventHandler.php index 69e2cddcd0..8a72c8fade 100644 --- a/app/Handlers/Events/UpdatedGroupEventHandler.php +++ b/app/Handlers/Events/UpdatedGroupEventHandler.php @@ -52,7 +52,7 @@ class UpdatedGroupEventHandler foreach ($journals as $journal) { $ruleGroupRepos->setUser($journal->user); - $groups = $ruleGroupRepos->getActiveGroups($journal->user); + $groups = $ruleGroupRepos->getActiveGroups(); /** @var RuleGroup $group */ foreach ($groups as $group) { diff --git a/app/Http/Controllers/Account/ShowController.php b/app/Http/Controllers/Account/ShowController.php index db60db7ab0..a3949e6392 100644 --- a/app/Http/Controllers/Account/ShowController.php +++ b/app/Http/Controllers/Account/ShowController.php @@ -25,6 +25,7 @@ namespace FireflyIII\Http\Controllers\Account; use Carbon\Carbon; use FireflyIII\Exceptions\FireflyException; +use FireflyIII\Helpers\Collector\GroupCollectorInterface; use FireflyIII\Helpers\Collector\TransactionCollectorInterface; use FireflyIII\Http\Controllers\Controller; use FireflyIII\Models\Account; @@ -76,8 +77,8 @@ class ShowController extends Controller /** * Show an account. * - * @param Request $request - * @param Account $account + * @param Request $request + * @param Account $account * @param Carbon|null $start * @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]); $chartUri = route('chart.account.period', [$account->id, $start->format('Y-m-d'), $end->format('Y-m-d')]); $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( 'accounts.show', compact( - 'account', 'showAll', 'what', 'currency', 'today', 'periods', 'subTitleIcon', 'transactions', 'subTitle', 'start', 'end', + 'account', 'showAll', 'what', 'currency', 'today', 'periods', 'subTitleIcon', 'groups', 'subTitle', 'start', 'end', 'chartUri' ) ); diff --git a/app/Models/RuleGroup.php b/app/Models/RuleGroup.php index 851747fbba..1686ed5bc0 100644 --- a/app/Models/RuleGroup.php +++ b/app/Models/RuleGroup.php @@ -34,19 +34,18 @@ use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; /** * Class RuleGroup. * - * @property bool $active - * @property User $user - * @property Carbon $created_at - * @property Carbon $updated_at - * @property string $title - * @property string $text - * @property int $id - * @property int $order + * @property bool $active + * @property User $user + * @property Carbon $created_at + * @property Carbon $updated_at + * @property string $title + * @property string $text + * @property int $id + * @property int $order * @property Collection $rules - * @property string description + * @property string description * @property \Illuminate\Support\Carbon|null $deleted_at * @property int $user_id - * @property string|null $description * @method static bool|null forceDelete() * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\RuleGroup newModelQuery() * @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\Query\Builder|\FireflyIII\Models\RuleGroup withTrashed() * @method static \Illuminate\Database\Query\Builder|\FireflyIII\Models\RuleGroup withoutTrashed() + * @property bool $stop_processing * @mixin \Eloquent */ class RuleGroup extends Model @@ -76,15 +76,16 @@ class RuleGroup extends Model */ protected $casts = [ - 'created_at' => 'datetime', - 'updated_at' => 'datetime', - 'deleted_at' => 'datetime', - 'active' => 'boolean', - 'order' => 'int', + 'created_at' => 'datetime', + 'updated_at' => 'datetime', + 'deleted_at' => 'datetime', + 'active' => 'boolean', + 'stop_processing' => 'boolean', + 'order' => 'int', ]; /** @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). diff --git a/app/Repositories/RuleGroup/RuleGroupRepository.php b/app/Repositories/RuleGroup/RuleGroupRepository.php index 1cb3b47905..bd2c8fcc82 100644 --- a/app/Repositories/RuleGroup/RuleGroupRepository.php +++ b/app/Repositories/RuleGroup/RuleGroupRepository.php @@ -56,7 +56,7 @@ class RuleGroupRepository implements RuleGroupRepositoryInterface } /** - * @param RuleGroup $ruleGroup + * @param RuleGroup $ruleGroup * @param RuleGroup|null $moveTo * * @return bool @@ -85,6 +85,49 @@ class RuleGroupRepository implements RuleGroupRepositoryInterface 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 * @@ -109,13 +152,11 @@ class RuleGroupRepository implements RuleGroupRepositoryInterface } /** - * @param User $user - * * @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.*']); } - /** - * @return int - */ - public function getHighestOrderRuleGroup(): int - { - $entry = $this->user->ruleGroups()->max('order'); - - return (int)$entry; - } - /** * @param User $user * @@ -253,49 +284,6 @@ class RuleGroupRepository implements RuleGroupRepositoryInterface 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 */ @@ -328,9 +316,19 @@ class RuleGroupRepository implements RuleGroupRepositoryInterface return $newRuleGroup; } + /** + * @return int + */ + public function getHighestOrderRuleGroup(): int + { + $entry = $this->user->ruleGroups()->max('order'); + + return (int)$entry; + } + /** * @param RuleGroup $ruleGroup - * @param array $data + * @param array $data * * @return RuleGroup */ diff --git a/app/Repositories/RuleGroup/RuleGroupRepositoryInterface.php b/app/Repositories/RuleGroup/RuleGroupRepositoryInterface.php index 17363456e0..fa0804ce89 100644 --- a/app/Repositories/RuleGroup/RuleGroupRepositoryInterface.php +++ b/app/Repositories/RuleGroup/RuleGroupRepositoryInterface.php @@ -59,11 +59,9 @@ interface RuleGroupRepositoryInterface public function get(): Collection; /** - * @param User $user - * * @return Collection */ - public function getActiveGroups(User $user): Collection; + public function getActiveGroups(): Collection; /** * @param RuleGroup $group diff --git a/app/Support/Http/Controllers/PeriodOverview.php b/app/Support/Http/Controllers/PeriodOverview.php index ef11025cc3..53c11ab1e0 100644 --- a/app/Support/Http/Controllers/PeriodOverview.php +++ b/app/Support/Http/Controllers/PeriodOverview.php @@ -25,6 +25,7 @@ namespace FireflyIII\Support\Http\Controllers; use Carbon\Carbon; use FireflyIII\Exceptions\FireflyException; +use FireflyIII\Helpers\Collector\GroupCollectorInterface; use FireflyIII\Helpers\Collector\GroupSumCollectorInterface; use FireflyIII\Helpers\Collector\TransactionCollectorInterface; use FireflyIII\Helpers\Filter\InternalTransferFilter; @@ -69,13 +70,12 @@ trait PeriodOverview * The method has been refactored recently for better performance. * * @param Account $account The account involved - * @param Carbon $date The start date. + * @param Carbon $date The start date. * * @return Collection */ protected function getAccountPeriodOverview(Account $account, Carbon $date): Collection { - throw new FireflyException('Is using collector.'); /** @var AccountRepositoryInterface $repository */ $repository = app(AccountRepositoryInterface::class); $range = app('preferences')->get('viewRange', '1M')->data; @@ -100,25 +100,30 @@ trait PeriodOverview $entries = new Collection; // loop dates 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 */ - $collector = app(TransactionCollectorInterface::class); - $collector->setAccounts(new Collection([$account]))->setRange($currentDate['start'], $currentDate['end'])->setTypes([TransactionType::WITHDRAWAL]) - ->withOpposingAccount(); - $spentSet = $collector->getTransactions(); + // collect from start to end: + /** @var GroupCollectorInterface $collector */ + $collector = app(GroupCollectorInterface::class); + $collector->setAccounts(new Collection([$account])); + $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); $title = app('navigation')->periodShow($currentDate['start'], $currentDate['period']); /** @noinspection PhpUndefinedMethodInspection */ $entries->push( [ - 'transactions' => 0, + 'transactions' => count($spentSet) + count($earnedSet), 'title' => $title, 'spent' => $spent, 'earned' => $earned, @@ -127,17 +132,44 @@ trait PeriodOverview ] ); } - - $cache->store($entries); + //$cache->store($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. * * @param Category $category - * @param Carbon $date + * @param Carbon $date * * @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. * - * @param Tag $tag + * @param Tag $tag * * @param Carbon $date * @@ -524,31 +556,4 @@ trait PeriodOverview 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; - } - } diff --git a/app/TransactionRules/Processor.php b/app/TransactionRules/Processor.php index ec0ded8198..c78aac93ac 100644 --- a/app/TransactionRules/Processor.php +++ b/app/TransactionRules/Processor.php @@ -62,26 +62,6 @@ class Processor $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 * @@ -127,6 +107,126 @@ class Processor 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 * 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; - } } diff --git a/database/migrations/2019_03_22_183214_changes_for_v480.php b/database/migrations/2019_03_22_183214_changes_for_v480.php index 00251b3105..a17fa40532 100644 --- a/database/migrations/2019_03_22_183214_changes_for_v480.php +++ b/database/migrations/2019_03_22_183214_changes_for_v480.php @@ -26,6 +26,9 @@ class ChangesForV480 extends Migration $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'); } ); + Schema::table('rule_groups', function (Blueprint $table) { + $table->boolean('stop_processing')->default(false); + }); } }