mirror of
				https://github.com/firefly-iii/firefly-iii.git
				synced 2025-10-31 02:36:28 +00:00 
			
		
		
		
	Replace transaction collector.
This commit is contained in:
		
							
								
								
									
										381
									
								
								app/Console/Commands/Tools/ApplyRules.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										381
									
								
								app/Console/Commands/Tools/ApplyRules.php
									
									
									
									
									
										Normal 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; | ||||
|     } | ||||
| } | ||||
| @@ -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) { | ||||
|   | ||||
| @@ -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) { | ||||
|   | ||||
| @@ -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' | ||||
|             ) | ||||
|         ); | ||||
|   | ||||
| @@ -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). | ||||
|   | ||||
| @@ -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 | ||||
|      */ | ||||
|   | ||||
| @@ -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 | ||||
|   | ||||
| @@ -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; | ||||
|     } | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -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; | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -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); | ||||
|         }); | ||||
|     } | ||||
| } | ||||
|   | ||||
		Reference in New Issue
	
	Block a user