diff --git a/app/Export/Processor.php b/app/Export/Processor.php index 5681a2aca9..41adf0722d 100644 --- a/app/Export/Processor.php +++ b/app/Export/Processor.php @@ -88,7 +88,7 @@ class Processor public function collectJournals() { $args = [$this->accounts, Auth::user(), $this->settings['startDate'], $this->settings['endDate']]; - $journalCollector = app('FireflyIII\Export\JournalCollector', $args); + $journalCollector = app('FireflyIII\Repositories\Journal\JournalCollector', $args); $this->journals = $journalCollector->collect(); Log::debug( 'Collected ' . diff --git a/app/Http/Controllers/RuleGroupController.php b/app/Http/Controllers/RuleGroupController.php index df1484df2e..6a29094a5b 100644 --- a/app/Http/Controllers/RuleGroupController.php +++ b/app/Http/Controllers/RuleGroupController.php @@ -4,10 +4,14 @@ declare(strict_types = 1); namespace FireflyIII\Http\Controllers; use Auth; +use Carbon\Carbon; use ExpandedForm; use FireflyIII\Http\Requests\RuleGroupFormRequest; +use FireflyIII\Http\Requests\SelectTransactionsRequest; +use FireflyIII\Jobs\ExecuteRuleGroupOnExistingTransactions; use FireflyIII\Models\RuleGroup; use FireflyIII\Repositories\RuleGroup\RuleGroupRepositoryInterface; +use FireflyIII\Repositories\Account\AccountRepositoryInterface; use Input; use Preferences; use Session; @@ -205,4 +209,47 @@ class RuleGroupController extends Controller } + /** + * Shows a form for the user to select a range of transactions to execute this rulegroup for + * @param RuleGroup $ruleGroup + */ + public function selectTransactions(AccountRepositoryInterface $repository, RuleGroup $ruleGroup) + { + // does the user have shared accounts? + $accounts = $repository->getAccounts(['Default account', 'Asset account']); + $accountList = ExpandedForm::makeSelectList($accounts); + $checkedAccounts = array_keys($accountList); + $first = session('first')->format('Y-m-d'); + $today = Carbon::create()->format('Y-m-d'); + + return view('rules.rule-group.select-transactions', compact('checkedAccounts', 'accountList', 'first', 'today', 'ruleGroup')); + } + + /** + * Execute the given rulegroup on a set of existing transactions + * @param RuleGroup $ruleGroup + */ + public function execute(SelectTransactionsRequest $request, AccountRepositoryInterface $repository, RuleGroup $ruleGroup) + { + // Get parameters specified by the user + $accounts = $repository->get($request->get('accounts')); + $startDate = new Carbon($request->get('start_date')); + $endDate = new Carbon($request->get('end_date')); + + // Create a job to do the work asynchronously + $job = new ExecuteRuleGroupOnExistingTransactions($ruleGroup); + + // Apply parameters to the job + $job->setUser(Auth::user()); + $job->setAccounts($accounts); + $job->setStartDate($startDate); + $job->setEndDate($endDate); + + // Dispatch a new job to execute it in a queue + $this->dispatch($job); + + // Tell the user that the job is queued + Session::flash('success', trans('firefly.executed_group_on_existing_transactions', ['title' => $ruleGroup->title])); + return redirect()->route('rules.index'); + } } diff --git a/app/Http/Requests/SelectTransactionsRequest.php b/app/Http/Requests/SelectTransactionsRequest.php new file mode 100644 index 0000000000..e62b53a578 --- /dev/null +++ b/app/Http/Requests/SelectTransactionsRequest.php @@ -0,0 +1,50 @@ +subDay()->format('Y-m-d'); + $today = Carbon::create()->addDay()->format('Y-m-d'); + + return [ + 'start_date' => 'required|date|after:' . $first, + 'end_date' => 'required|date|before:' . $today, + 'accounts' => 'required', + 'accounts.*' => 'required|exists:accounts,id|belongsToUser:accounts', + ]; + } +} diff --git a/app/Http/routes.php b/app/Http/routes.php index 7e66a5a452..658bca4b0e 100644 --- a/app/Http/routes.php +++ b/app/Http/routes.php @@ -289,12 +289,14 @@ Route::group( Route::get('/rules/groups/delete/{ruleGroup}', ['uses' => 'RuleGroupController@delete', 'as' => 'rules.rule-group.delete']); Route::get('/rules/groups/up/{ruleGroup}', ['uses' => 'RuleGroupController@up', 'as' => 'rules.rule-group.up']); Route::get('/rules/groups/down/{ruleGroup}', ['uses' => 'RuleGroupController@down', 'as' => 'rules.rule-group.down']); - + Route::get('/rules/groups/select_transactions/{ruleGroup}', ['uses' => 'RuleGroupController@selectTransactions', 'as' => 'rules.rule-group.select_transactions']); + // rule groups POST Route::post('/rules/groups/store', ['uses' => 'RuleGroupController@store', 'as' => 'rules.rule-group.store']); Route::post('/rules/groups/update/{ruleGroup}', ['uses' => 'RuleGroupController@update', 'as' => 'rules.rule-group.update']); Route::post('/rules/groups/destroy/{ruleGroup}', ['uses' => 'RuleGroupController@destroy', 'as' => 'rules.rule-group.destroy']); - + Route::post('/rules/groups/execute/{ruleGroup}', ['uses' => 'RuleGroupController@execute', 'as' => 'rules.rule-group.execute']); + /** * Search Controller */ diff --git a/app/Jobs/ExecuteRuleGroupOnExistingTransactions.php b/app/Jobs/ExecuteRuleGroupOnExistingTransactions.php new file mode 100644 index 0000000000..746f36d2d3 --- /dev/null +++ b/app/Jobs/ExecuteRuleGroupOnExistingTransactions.php @@ -0,0 +1,158 @@ +ruleGroup = $ruleGroup; + } + + /** + * Execute the job. + * + * @return void + */ + public function handle() + { + // Lookup all journals that match the parameters specified + $journals = $this->collectJournals(); + + // Find processors for each rule within the current rule group + $processors = $this->collectProcessors(); + + // Execute the rules for each transaction + foreach($journals as $journal) { + /** @var Processor $processor */ + foreach ($processors as $processor) { + $processor->handleTransactionJournal($journal); + + // Stop processing this group if the rule specifies 'stop_processing' + if ($processor->getRule()->stop_processing) { + break; + } + } + } + } + + /** + * Collect all journals that should be processed + * @return Collection + */ + protected function collectJournals() + { + $args = [$this->accounts, $this->user, $this->startDate, $this->endDate]; + $journalCollector = app('FireflyIII\Repositories\Journal\JournalCollector', $args); + return $journalCollector->collect(); + } + + /** + * Collects a list of rule processors, one for each rule within the rule group + * @return array + */ + protected function collectProcessors() { + // Find all rules belonging to this rulegroup + $rules = $this->ruleGroup->rules() + ->leftJoin('rule_triggers', 'rules.id', '=', 'rule_triggers.rule_id') + ->where('rule_triggers.trigger_type', 'user_action') + ->where('rule_triggers.trigger_value', 'store-journal') + ->where('rules.active', 1) + ->get(['rules.*']); + + // Create a list of processors for these rules + return array_map( function( $rule ) { + return Processor::make($rule); + }, $rules->all()); + } + + /** + * @return User + */ + public function getUser() { + return $this->user; + } + + /** + * + * @param User $user + */ + public function setUser(User $user) { + $this->user = $user; + } + + /** + * @return Collection + */ + public function getAccounts() { + return $this->accounts; + } + + /** + * + * @param Carbon $user + */ + public function setAccounts(Collection $accounts) { + $this->accounts = $accounts; + } + + /** + * @return \Carbon\Carbon + */ + public function getStartDate() { + return $this->startDate; + } + + /** + * + * @param Carbon $date + */ + public function setStartDate(Carbon $date) { + $this->startDate = $date; + } + + /** + * @return \Carbon\Carbon + */ + public function getEndDate() { + return $this->endDate; + } + + /** + * + * @param Carbon $date + */ + public function setEndDate(Carbon $date) { + $this->endDate = $date; + } + +} diff --git a/app/Export/JournalCollector.php b/app/Repositories/Journal/JournalCollector.php similarity index 97% rename from app/Export/JournalCollector.php rename to app/Repositories/Journal/JournalCollector.php index 14cb30f654..f1e15fdd32 100644 --- a/app/Export/JournalCollector.php +++ b/app/Repositories/Journal/JournalCollector.php @@ -8,7 +8,7 @@ declare(strict_types = 1); * of the MIT license. See the LICENSE file for details. */ -namespace FireflyIII\Export; +namespace FireflyIII\Repositories\Journal; use Carbon\Carbon; use FireflyIII\User; diff --git a/app/Rules/Processor.php b/app/Rules/Processor.php index 02b7bac5b5..6be99aee43 100644 --- a/app/Rules/Processor.php +++ b/app/Rules/Processor.php @@ -140,6 +140,14 @@ final class Processor } + /** + * + * @return \FireflyIII\Models\Rule + */ + public function getRule() { + return $this->rule; + } + /** * @return bool */ diff --git a/resources/lang/en_US/firefly.php b/resources/lang/en_US/firefly.php index b6b8f96d18..86f7b04691 100644 --- a/resources/lang/en_US/firefly.php +++ b/resources/lang/en_US/firefly.php @@ -133,6 +133,13 @@ return [ 'warning_no_matching_transactions' => 'No matching transactions found. Please note that for performance reasons, only the last :num_transactions transactions have been checked.', 'warning_no_valid_triggers' => 'No valid triggers provided.', + 'execute_on_existing_transactions' => 'Execute for existing transactions', + 'execute_on_existing_transactions_intro' => 'When a rule or group has been changed or added, you can execute it for existing transactions', + 'execute_on_existing_transactions_short' => 'Existing transactions', + 'executed_group_on_existing_transactions' => 'Executed group ":title" for existing transactions', + 'include_transactions_from_accounts' => 'Include transactions from these accounts', + 'execute' => 'Execute', + // actions and triggers 'rule_trigger_user_action' => 'User action is ":trigger_value"', 'rule_trigger_from_account_starts' => 'Source account starts with ":trigger_value"', diff --git a/resources/lang/nl_NL/firefly.php b/resources/lang/nl_NL/firefly.php index 825880e100..253317581d 100755 --- a/resources/lang/nl_NL/firefly.php +++ b/resources/lang/nl_NL/firefly.php @@ -131,6 +131,13 @@ return [ 'warning_transaction_subset' => 'Je ziet hier maximaal :max_num_transactions transacties omdat je anders veel te lang moet wachten', 'warning_no_matching_transactions' => 'Niks gevonden in je laatste :num_transactions transacties.', 'warning_no_valid_triggers' => 'Geen geldige triggers gevonden.', + + 'execute_on_existing_transactions' => 'Uitvoeren op bestaande transacties', + 'execute_on_existing_transactions_intro' => 'Wanneer een regel of groep is veranderd of toegevoegd, kun je hem hier uitvoeren voor bestaande transacties.', + 'execute_on_existing_transactions_short' => 'Bestaande transacties', + 'executed_group_on_existing_transactions' => 'Groep :title is uitgevoerd op bestaande transacties', + 'include_transactions_from_accounts' => 'Gebruik transacties van deze rekeningen', + 'execute' => 'Uitvoeren', // actions and triggers 'rule_trigger_user_action' => 'Gebruikersactie is ":trigger_value"', diff --git a/resources/views/rules/index.twig b/resources/views/rules/index.twig index 447cf3d4eb..df5e4a631d 100644 --- a/resources/views/rules/index.twig +++ b/resources/views/rules/index.twig @@ -47,6 +47,8 @@ class="fa fa-fw fa-pencil"> {{ 'edit'|_ }}