. */ namespace FireflyIII\TransactionRules\Engine; use FireflyIII\Exceptions\FireflyException; use FireflyIII\Models\Rule; use FireflyIII\Models\RuleGroup; use FireflyIII\Models\TransactionJournal; use FireflyIII\Repositories\RuleGroup\RuleGroupRepository; use FireflyIII\TransactionRules\Processor; use FireflyIII\User; use Illuminate\Support\Collection; use Log; /** * Class RuleEngine * * Set the user, then apply an array to setRulesToApply(array) or call addRuleIdToApply(int) or addRuleToApply(Rule). * Then call process() to make the magic happen. * */ class RuleEngine { /** @var Collection */ private $ruleGroups; /** @var array */ private $rulesToApply; /** @var bool */ private $allRules; /** @var User */ private $user; /** @var RuleGroupRepository */ private $ruleGroupRepository; /** * RuleEngine constructor. */ public function __construct() { Log::debug('Created RuleEngine'); $this->ruleGroups = new Collection; $this->rulesToApply = []; $this->allRules = false; $this->ruleGroupRepository = app(RuleGroupRepository::class); } /** * @param bool $allRules */ public function setAllRules(bool $allRules): void { Log::debug('RuleEngine will apply ALL rules.'); $this->allRules = $allRules; } /** * @param array $rulesToApply */ public function setRulesToApply(array $rulesToApply): void { Log::debug('RuleEngine will try rules', $rulesToApply); $this->rulesToApply = $rulesToApply; } /** * @param User $user */ public function setUser(User $user): void { $this->user = $user; $this->ruleGroupRepository->setUser($user); $this->ruleGroups = $this->ruleGroupRepository->getActiveGroups(); } /** * @param TransactionJournal $transactionJournal */ public function processTransactionJournal(TransactionJournal $transactionJournal): void { Log::debug(sprintf('Will process transaction journal #%d ("%s")', $transactionJournal->id, $transactionJournal->description)); /** @var RuleGroup $group */ foreach ($this->ruleGroups as $group) { Log::debug(sprintf('Now at rule group #%d', $group->id)); $groupTriggered = false; /** @var Rule $rule */ foreach ($group->rules as $rule) { Log::debug(sprintf('Now at rule #%d from rule group #%d', $rule->id, $group->id)); $ruleTriggered = false; // if in rule selection, or group in selection or all rules, it's included. if ($this->includeRule($rule)) { Log::debug(sprintf('Rule #%d is included.', $rule->id)); /** @var Processor $processor */ $processor = app(Processor::class); $ruleTriggered = false; try { $processor->make($rule, true); $ruleTriggered = $processor->handleTransactionJournal($transactionJournal); } catch (FireflyException $e) { Log::error($e->getMessage()); } if ($ruleTriggered) { Log::debug('The rule was triggered, so the group is as well!'); $groupTriggered = true; } } if (!$this->includeRule($rule)) { Log::debug(sprintf('Rule #%d is not included.', $rule->id)); } // if the rule is triggered and stop processing is true, cancel the entire group. if ($ruleTriggered && $rule->stop_processing) { Log::info(sprintf('Break out group #%d because rule #%d was triggered.', $group->id, $rule->id)); break; } } // if group is triggered and stop processing is true, cancel the whole thing. if ($groupTriggered && $group->stop_processing) { Log::info(sprintf('Break out ALL because group #%d was triggered.', $group->id)); break; } } Log::debug('Done processing this transaction journal.'); } /** * @param array $journal */ public function processJournalArray(array $journal): void { Log::debug(sprintf('Will process transaction journal #%d ("%s")', $journal['id'], $journal['description'])); /** @var RuleGroup $group */ foreach ($this->ruleGroups as $group) { Log::debug(sprintf('Now at rule group #%d', $group->id)); $groupTriggered = false; /** @var Rule $rule */ foreach ($group->rules as $rule) { Log::debug(sprintf('Now at rule #%d from rule group #%d', $rule->id, $group->id)); $ruleTriggered = false; // if in rule selection, or group in selection or all rules, it's included. if ($this->includeRule($rule)) { Log::debug(sprintf('Rule #%d is included.', $rule->id)); /** @var Processor $processor */ $processor = app(Processor::class); $ruleTriggered = false; try { $processor->make($rule, true); $ruleTriggered = $processor->handleJournalArray($journal); } catch (FireflyException $e) { Log::error($e->getMessage()); } if ($ruleTriggered) { Log::debug('The rule was triggered, so the group is as well!'); $groupTriggered = true; } } if (!$this->includeRule($rule)) { Log::debug(sprintf('Rule #%d is not included.', $rule->id)); } // if the rule is triggered and stop processing is true, cancel the entire group. if ($ruleTriggered && $rule->stop_processing) { Log::info(sprintf('Break out group #%d because rule #%d was triggered.', $group->id, $rule->id)); break; } } // if group is triggered and stop processing is true, cancel the whole thing. if ($groupTriggered && $group->stop_processing) { Log::info(sprintf('Break out ALL because group #%d was triggered.', $group->id)); break; } } Log::debug('Done processing this transaction journal.'); } /** * @param Rule $rule * @return bool */ private function includeRule(Rule $rule): bool { return $this->allRules || in_array($rule->id, $this->rulesToApply, true); } }