From 3bc38570a2dc860a8887550cd648e8083ecc9d28 Mon Sep 17 00:00:00 2001 From: James Cole Date: Sat, 13 Oct 2018 21:32:20 +0200 Subject: [PATCH] Code for #1071 --- app/Console/Commands/ApplyRules.php | 350 +++++++++++++++++++ app/Console/Commands/VerifiesAccessToken.php | 20 ++ 2 files changed, 370 insertions(+) create mode 100644 app/Console/Commands/ApplyRules.php diff --git a/app/Console/Commands/ApplyRules.php b/app/Console/Commands/ApplyRules.php new file mode 100644 index 0000000000..8569c149be --- /dev/null +++ b/app/Console/Commands/ApplyRules.php @@ -0,0 +1,350 @@ +accounts = new Collection; + $this->rules = new Collection; + } + + /** + * Execute the console command. + * + * @return int + * @throws \FireflyIII\Exceptions\FireflyException + */ + public function handle(): int + { + if (!$this->verifyAccessToken()) { + $this->error('Invalid access token.'); + + return 1; + } + + $result = $this->verifyInput(); + if (false === $result) { + return 1; + } + + // get transactions from asset accounts. + /** @var TransactionCollectorInterface $collector */ + $collector = app(TransactionCollectorInterface::class); + $collector->setUser($this->getUser()); + $collector->setAccounts($this->accounts); + $collector->setRange($this->startDate, $this->endDate); + $transactions = $collector->getTransactions(); + $count = $transactions->count(); + $bar = $this->output->createProgressBar($count * $this->rules->count()); + $results = new Collection; + $this->line(sprintf('Will apply %d rules to %d transactions', $this->rules->count(), $transactions->count())); + + + foreach ($this->rules as $rule) { + /** @var Processor $processor */ + $processor = app(Processor::class); + $processor->make($rule, true); + + /** @var Transaction $transaction */ + foreach ($transactions as $transaction) { + /** @var Rule $rule */ + $bar->advance(); + $result = $processor->handleTransaction($transaction); + if (true === $result) { + $results->push($transaction); + } + } + } + $results = $results->unique( + function (Transaction $transaction) { + return (int)$transaction->journal_id; + } + ); + + $bar->finish(); + $this->line(''); + if (0 === \count($results)) { + $this->line('The rules were fired but did not influence any transactions.'); + } + if (\count($results) > 0) { + $this->line(sprintf('The rules were fired, and influences %d transaction(s).', \count($results))); + foreach ($results as $result) { + $this->line( + vsprintf( + 'Transaction #%d: "%s" (%s %s)', + [ + $result->journal_id, + $result->description, + $result->transaction_currency_code, + round($result->transaction_amount, $result->transaction_currency_dp), + ] + ) + ); + } + } + + return 0; + } + + /** + * + */ + private function grabAllRules(): void + { + if (true === $this->option('all_rules')) { + /** @var RuleRepositoryInterface $ruleRepos */ + $ruleRepos = app(RuleRepositoryInterface::class); + $ruleRepos->setUser($this->getUser()); + $this->rules = $ruleRepos->getAll(); + } + } + + /** + * + */ + private function parseDates(): void + { + // parse start date. + $startDate = Carbon::create()->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; + } + + /** + * @return bool + * @throws \FireflyIII\Exceptions\FireflyException + */ + private function verifyInput(): bool + { + // verify account. + $result = $this->verifyInputAccounts(); + if (false === $result) { + return $result; + } + + // verify rule groups. + $result = $this->verifyRuleGroups(); + if (false === $result) { + return $result; + } + + // verify rules. + $result = $this->verifyRules(); + if (false === $result) { + return $result; + } + + $this->grabAllRules(); + $this->parseDates(); + + //$this->line('Number of rules found: ' . $this->rules->count()); + $this->line('Start date is ' . $this->startDate->format('Y-m-d')); + $this->line('End date is ' . $this->endDate->format('Y-m-d')); + + return true; + } + + /** + * @return bool + * @throws \FireflyIII\Exceptions\FireflyException + */ + private function verifyInputAccounts(): bool + { + $accountString = $this->option('accounts'); + if (null === $accountString || '' === $accountString) { + $this->error('Please use the --accounts 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 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, [AccountType::DEFAULT, AccountType::DEBT, AccountType::ASSET, AccountType::LOAN, AccountType::MORTGAGE], 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 + * @throws \FireflyIII\Exceptions\FireflyException + */ + private function verifyRuleGroups(): bool + { + $ruleGroupString = $this->option('rule_groups'); + if (null === $ruleGroupString || '' === $ruleGroupString) { + // can be empty. + return true; + } + $finalList = new Collection; + $ruleGroupList = explode(',', $ruleGroupString); + + if (0 === \count($ruleGroupList)) { + // can be empty. + + return true; + } + /** @var RuleGroupRepositoryInterface $ruleGroupRepos */ + $ruleGroupRepos = app(RuleGroupRepositoryInterface::class); + $ruleGroupRepos->setUser($this->getUser()); + + foreach ($ruleGroupList as $ruleGroupId) { + $ruleGroupId = (int)$ruleGroupId; + $ruleGroup = $ruleGroupRepos->find($ruleGroupId); + if (null !== $ruleGroup) { + $rules = $ruleGroupRepos->getActiveStoreRules($ruleGroup); + $finalList = $finalList->merge($rules); + } + } + $this->rules = $finalList; + + return true; + } + + /** + * @return bool + * @throws \FireflyIII\Exceptions\FireflyException + */ + private function verifyRules(): bool + { + $ruleString = $this->option('rules'); + if (null === $ruleString || '' === $ruleString) { + // can be empty. + return true; + } + $finalList = new Collection; + $ruleList = explode(',', $ruleString); + + if (0 === \count($ruleList)) { + // can be empty. + + return true; + } + /** @var RuleRepositoryInterface $ruleRepos */ + $ruleRepos = app(RuleRepositoryInterface::class); + $ruleRepos->setUser($this->getUser()); + + foreach ($ruleList as $ruleId) { + $ruleId = (int)$ruleId; + $rule = $ruleRepos->find($ruleId); + if (null !== $rule) { + $finalList->push($rule); + } + } + if ($finalList->count() > 0) { + $this->rules = $finalList; + } + + return true; + } + + +} diff --git a/app/Console/Commands/VerifiesAccessToken.php b/app/Console/Commands/VerifiesAccessToken.php index a0224ae0d6..39f300d796 100644 --- a/app/Console/Commands/VerifiesAccessToken.php +++ b/app/Console/Commands/VerifiesAccessToken.php @@ -23,17 +23,37 @@ declare(strict_types=1); namespace FireflyIII\Console\Commands; +use FireflyIII\Exceptions\FireflyException; use FireflyIII\Repositories\User\UserRepositoryInterface; +use FireflyIII\User; use Log; /** * Trait VerifiesAccessToken. * * Verifies user access token for sensitive commands. + * * @codeCoverageIgnore */ trait VerifiesAccessToken { + /** + * @return User + * @throws FireflyException + */ + public function getUser(): User + { + $userId = (int)$this->option('user'); + /** @var UserRepositoryInterface $repository */ + $repository = app(UserRepositoryInterface::class); + $user = $repository->findNull($userId); + if (null === $user) { + throw new FireflyException('User is unexpectedly NULL'); + } + + return $user; + } + /** * Abstract method to make sure trait knows about method "option". *