diff --git a/.ci/php-cs-fixer/composer.lock b/.ci/php-cs-fixer/composer.lock index 8d5352642d..47c6cae3f1 100644 --- a/.ci/php-cs-fixer/composer.lock +++ b/.ci/php-cs-fixer/composer.lock @@ -2687,5 +2687,8 @@ "php": ">=8.5.0" }, "platform-dev": {}, + "platform-overrides": { + "php": "8.5" + }, "plugin-api-version": "2.9.0" } diff --git a/app/Api/V1/Controllers/Models/Transaction/UpdateController.php b/app/Api/V1/Controllers/Models/Transaction/UpdateController.php index f5f1768280..c3b259e90f 100644 --- a/app/Api/V1/Controllers/Models/Transaction/UpdateController.php +++ b/app/Api/V1/Controllers/Models/Transaction/UpdateController.php @@ -74,8 +74,8 @@ class UpdateController extends Controller */ public function update(UpdateRequest $request, TransactionGroup $transactionGroup): JsonResponse { - Log::debug('Now in update routine for transaction group'); $data = $request->getAll(); + Log::debug('Now in update routine for transaction group', $data); $oldHash = $this->groupRepository->getCompareHash($transactionGroup); $objects = TransactionGroupEventObjects::collectFromTransactionGroup($transactionGroup); $transactionGroup = $this->groupRepository->update($transactionGroup, $data); @@ -92,6 +92,7 @@ class UpdateController extends Controller $flags->applyRules = $applyRules; $flags->fireWebhooks = $fireWebhooks; $flags->recalculateCredit = $runRecalculations; + $flags->batchSubmission = $data['batch_submission'] ?? false; event(new UpdatedSingleTransactionGroup($flags, $objects)); event(new WebhookMessagesRequestSending()); diff --git a/app/Api/V1/Controllers/System/BatchController.php b/app/Api/V1/Controllers/System/BatchController.php index 1eb48d4b6a..cd39d9a77e 100644 --- a/app/Api/V1/Controllers/System/BatchController.php +++ b/app/Api/V1/Controllers/System/BatchController.php @@ -31,6 +31,7 @@ use FireflyIII\Models\TransactionJournal; use FireflyIII\Repositories\Journal\JournalRepositoryInterface; use Illuminate\Http\JsonResponse; use Illuminate\Http\Request; +use Illuminate\Support\Facades\Log; class BatchController extends Controller { @@ -52,19 +53,22 @@ class BatchController extends Controller public function finishBatch(Request $request): JsonResponse { - $journals = $this->repository->getUncompletedJournals(); + Log::debug('Now in finishBatch.'); + $journals = $this->repository->getUncompletedJournals(); if (0 === count($journals)) { + Log::debug('Counted zero journals, return.'); return response()->json([], 204); } - + Log::debug(sprintf('Counted %d journals.', count($journals))); /** @var TransactionJournal $first */ - $first = $journals->first(); - $group = $first?->transactionGroup; + $first = $journals->first(); + $group = $first?->transactionGroup; if (null === $group) { + Log::debug('First group is NULL.'); return response()->json([], 204); } $flags = new TransactionGroupEventFlags(); - $flags->applyRules = 'true' === $request->get('apply_rules'); + $flags->applyRules = 'true' === $request->input('apply_rules'); event(new UserRequestedBatchProcessing($flags)); // event(new CreatedSingleTransactionGroup($group, $flags)); diff --git a/app/Events/Model/TransactionGroup/UserRequestedBatchProcessing.php b/app/Events/Model/TransactionGroup/UserRequestedBatchProcessing.php index 4df1c6fe37..e049a3dde1 100644 --- a/app/Events/Model/TransactionGroup/UserRequestedBatchProcessing.php +++ b/app/Events/Model/TransactionGroup/UserRequestedBatchProcessing.php @@ -30,9 +30,7 @@ class UserRequestedBatchProcessing extends Event { public TransactionGroupEventObjects $objects; - public function __construct( - public TransactionGroupEventFlags $flags - ) { + public function __construct(public TransactionGroupEventFlags $flags) { $this->objects = new TransactionGroupEventObjects(); } } diff --git a/app/Helpers/Collector/GroupCollector.php b/app/Helpers/Collector/GroupCollector.php index 94bb51c858..9b3c9e6e1b 100644 --- a/app/Helpers/Collector/GroupCollector.php +++ b/app/Helpers/Collector/GroupCollector.php @@ -603,7 +603,7 @@ class GroupCollector implements GroupCollectorInterface public function setSearchWords(array $array): GroupCollectorInterface { if (0 === count($array)) { - Log::debug('No words in array'); + // Log::debug('No words in array'); return $this; } diff --git a/app/Listeners/Model/TransactionGroup/SupportsGroupProcessingTrait.php b/app/Listeners/Model/TransactionGroup/SupportsGroupProcessingTrait.php index 33ce0d9116..26cc3b66a8 100644 --- a/app/Listeners/Model/TransactionGroup/SupportsGroupProcessingTrait.php +++ b/app/Listeners/Model/TransactionGroup/SupportsGroupProcessingTrait.php @@ -54,9 +54,13 @@ trait SupportsGroupProcessingTrait // create and fire rule engine. $newRuleEngine = app(RuleEngineInterface::class); $newRuleEngine->setUser($user); - $newRuleEngine->addOperator(['type' => 'journal_id', 'value' => $journalIds]); $newRuleEngine->setRuleGroups($groups); - $newRuleEngine->fire(); + foreach($array as $journalId) { + $newRuleEngine->removeOperator('journal_id'); + $newRuleEngine->addOperator(['type' => 'journal_id', 'value' => $journalId]); + $newRuleEngine->fire(); + } + Log::debug(sprintf('Done with processRules("%s") for %d journal(s)', $type, $set->count())); } diff --git a/app/Support/Search/OperatorQuerySearch.php b/app/Support/Search/OperatorQuerySearch.php index 6d25aab8b6..e3893578b7 100644 --- a/app/Support/Search/OperatorQuerySearch.php +++ b/app/Support/Search/OperatorQuerySearch.php @@ -178,7 +178,7 @@ class OperatorQuerySearch implements SearchInterface /** @var QueryParserInterface $parser */ $parser = app(QueryParserInterface::class); - Log::debug(sprintf('Using %s as implementation for QueryParserInterface', $parser::class)); + // Log::debug(sprintf('Using %s as implementation for QueryParserInterface', $parser::class)); try { $parsedQuery = $parser->parse($query); @@ -189,7 +189,7 @@ class OperatorQuerySearch implements SearchInterface throw new FireflyException(sprintf('Invalid search value "%s". See the logs.', e($query)), 0, $e); } - Log::debug(sprintf('Found %d node(s) at top-level', count($parsedQuery->getNodes()))); + // Log::debug(sprintf('Found %d node(s) at top-level', count($parsedQuery->getNodes()))); $this->handleSearchNode($parsedQuery, $parsedQuery->isProhibited(false)); // add missing information @@ -329,7 +329,7 @@ class OperatorQuerySearch implements SearchInterface */ private function handleSearchNode(Node $node, bool $flipProhibitedFlag): void { - Log::debug(sprintf('Now in handleSearchNode(%s)', $node::class)); + // Log::debug(sprintf('Now in handleSearchNode(%s)', $node::class)); switch (true) { case $node instanceof StringNode: diff --git a/app/Support/Search/QueryParser/QueryParser.php b/app/Support/Search/QueryParser/QueryParser.php index cb5f7db57e..5604e649fb 100644 --- a/app/Support/Search/QueryParser/QueryParser.php +++ b/app/Support/Search/QueryParser/QueryParser.php @@ -145,7 +145,7 @@ class QueryParser implements QueryParserInterface $skipNext = true; } if ('' !== $tokenUnderConstruction && !$skipNext) { // @phpstan-ignore-line - Log::debug(sprintf('Turns out that "%s" is a field name. Reset the token.', $tokenUnderConstruction)); + // Log::debug(sprintf('Turns out that "%s" is a field name. Reset the token.', $tokenUnderConstruction)); // If we meet a colon with a left-hand side string, we know we're in a field and are about to set up the value $fieldName = $tokenUnderConstruction; $tokenUnderConstruction = ''; diff --git a/app/TransactionRules/Engine/RuleEngineInterface.php b/app/TransactionRules/Engine/RuleEngineInterface.php index 53ee3e1915..7c15468b5c 100644 --- a/app/TransactionRules/Engine/RuleEngineInterface.php +++ b/app/TransactionRules/Engine/RuleEngineInterface.php @@ -37,6 +37,9 @@ interface RuleEngineInterface */ public function addOperator(array $operator): void; + public function removeOperator(string $type): void; + + /** * Find all transactions only, dont apply anything. */ diff --git a/app/TransactionRules/Engine/SearchRuleEngine.php b/app/TransactionRules/Engine/SearchRuleEngine.php index a5d9baf030..79e11f76f4 100644 --- a/app/TransactionRules/Engine/SearchRuleEngine.php +++ b/app/TransactionRules/Engine/SearchRuleEngine.php @@ -45,12 +45,13 @@ use Illuminate\Support\Facades\Log; class SearchRuleEngine implements RuleEngineInterface { private readonly Collection $groups; - private array $operators = []; + + private array $operators = []; // always collect the triggers from the database, unless indicated otherwise. - private bool $refreshTriggers = true; - private array $resultCount = []; + private bool $refreshTriggers = true; + private array $resultCount = []; private readonly Collection $rules; - private User $user; + private User $user; public function __construct() { @@ -69,7 +70,7 @@ class SearchRuleEngine implements RuleEngineInterface Log::debug('SearchRuleEngine::find()'); $collection = new Collection(); foreach ($this->rules as $rule) { - $found = new Collection(); + $found = new Collection(); if (true === $rule->strict) { $found = $this->findStrictRule($rule); } @@ -78,7 +79,7 @@ class SearchRuleEngine implements RuleEngineInterface } $collection = $collection->merge($found); } - $result = $collection->unique(); + $result = $collection->unique(); Log::debug(sprintf('SearchRuleEngine::find() returns %d unique transactions.', $result->count())); return $result; @@ -101,9 +102,9 @@ class SearchRuleEngine implements RuleEngineInterface $result = $this->fireRule($rule); if ($result && true === $rule->stop_processing) { Log::debug(sprintf( - 'Rule #%d has triggered and executed, but calls to stop processing. Since not in the context of a group, do not stop.', - $rule->id - )); + 'Rule #%d has triggered and executed, but calls to stop processing. Since not in the context of a group, do not stop.', + $rule->id + )); } if (false === $result && true === $rule->stop_processing) { Log::debug(sprintf('Rule #%d has triggered and changed nothing, but calls to stop processing. Do not stop.', $rule->id)); @@ -169,10 +170,9 @@ class SearchRuleEngine implements RuleEngineInterface private function addNotes(array $transaction): array { $transaction['notes'] = ''; - $dbNote = Note::where('noteable_id', (int) $transaction['transaction_journal_id']) - ->where('noteable_type', TransactionJournal::class) - ->first(['notes.*']) - ; + $dbNote = Note::where('noteable_id', (int)$transaction['transaction_journal_id']) + ->where('noteable_type', TransactionJournal::class) + ->first(['notes.*']); if (null !== $dbNote) { $transaction['notes'] = $dbNote->text; } @@ -201,12 +201,12 @@ class SearchRuleEngine implements RuleEngineInterface /** @var RuleTrigger $ruleTrigger */ foreach ($triggers as $ruleTrigger) { Log::debug(sprintf( - 'Now at rule trigger #%d: %s:"%s" (%s).', - $ruleTrigger->id, - $ruleTrigger->trigger_type, - $ruleTrigger->trigger_value, - var_export($ruleTrigger->stop_processing, true) - )); + 'Now at rule trigger #%d: %s:"%s" (%s).', + $ruleTrigger->id, + $ruleTrigger->trigger_type, + $ruleTrigger->trigger_value, + var_export($ruleTrigger->stop_processing, true) + )); if (false === $ruleTrigger->active) { Log::debug('Trigger is not active, continue.'); @@ -244,10 +244,10 @@ class SearchRuleEngine implements RuleEngineInterface $searchEngine->parseQuery(sprintf('%s:%s', $type, $value)); } - $result = $searchEngine->searchTransactions(); - $collection = $result->getCollection(); + $result = $searchEngine->searchTransactions(); + $collection = $result->getCollection(); Log::debug(sprintf('Found in this run, %d transactions', $collection->count())); - $total = $total->merge($collection); + $total = $total->merge($collection); Log::debug(sprintf('Total collection is now %d transactions', $total->count())); ++$count; // if trigger says stop processing, do so. @@ -261,7 +261,7 @@ class SearchRuleEngine implements RuleEngineInterface Log::debug(sprintf('Done running %d trigger(s)', $count)); // make collection unique - $unique = $total->unique(static function (array $group): string { + $unique = $total->unique(static function (array $group): string { $str = ''; foreach ($group['transactions'] as $transaction) { $str = sprintf('%s%d', $str, $transaction['transaction_journal_id']); @@ -283,8 +283,8 @@ class SearchRuleEngine implements RuleEngineInterface private function findStrictRule(Rule $rule): Collection { Log::debug(sprintf('Now in findStrictRule(#%d)', $rule->id ?? 0)); - $searchArray = []; - $triggers = []; + $searchArray = []; + $triggers = []; if ($this->refreshTriggers) { $triggers = $rule->ruleTriggers()->orderBy('order', 'ASC')->get(); } @@ -298,12 +298,12 @@ class SearchRuleEngine implements RuleEngineInterface continue; } $contextSearch = $ruleTrigger->trigger_type; - if (str_starts_with((string) $ruleTrigger->trigger_type, '-')) { - $contextSearch = substr((string) $ruleTrigger->trigger_type, 1); + if (str_starts_with((string)$ruleTrigger->trigger_type, '-')) { + $contextSearch = substr((string)$ruleTrigger->trigger_type, 1); } // if the trigger needs no context, value is different: - $needsContext = (bool) (config(sprintf('search.operators.%s.needs_context', $contextSearch)) ?? true); + $needsContext = (bool)(config(sprintf('search.operators.%s.needs_context', $contextSearch)) ?? true); if (false === $needsContext) { Log::debug(sprintf('SearchRuleEngine:: add a rule trigger (no context): %s:true', $ruleTrigger->trigger_type)); $searchArray[$ruleTrigger->trigger_type][] = 'true'; @@ -319,7 +319,7 @@ class SearchRuleEngine implements RuleEngineInterface Log::debug(sprintf('SearchRuleEngine:: add local added operator: %s:"%s"', $operator['type'], $operator['value'])); $searchArray[$operator['type']][] = sprintf('"%s"', $operator['value']); } - $date = today(config('app.timezone')); + $date = today(config('app.timezone')); if ($this->hasSpecificJournalTrigger($searchArray)) { $date = $this->setDateFromJournalTrigger($searchArray); } @@ -331,6 +331,7 @@ class SearchRuleEngine implements RuleEngineInterface $searchEngine->setLimit(31337); $searchEngine->setDate($date); Log::debug('Search array', $searchArray); + foreach ($searchArray as $type => $searches) { foreach ($searches as $value) { $query = sprintf('%s:%s', $type, $value); @@ -339,7 +340,7 @@ class SearchRuleEngine implements RuleEngineInterface } } - $result = $searchEngine->searchTransactions(); + $result = $searchEngine->searchTransactions(); return $result->getCollection(); } @@ -359,12 +360,8 @@ class SearchRuleEngine implements RuleEngineInterface $rules = $group ->rules() ->orderBy('rules.order', 'ASC') - // ->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', true) - ->get(['rules.*']) - ; + ->get(['rules.*']); } Log::debug(sprintf('Going to fire group #%d with %d rule(s)', $group->id, $rules->count())); @@ -432,7 +429,7 @@ class SearchRuleEngine implements RuleEngineInterface $this->processResults($rule, $collection); - $result = $collection->count() > 0; + $result = $collection->count() > 0; if ($result) { Log::debug(sprintf('SearchRuleEngine:: Done. Rule #%d was triggered (on %d transaction(s)).', $rule->id, $collection->count())); @@ -458,12 +455,16 @@ class SearchRuleEngine implements RuleEngineInterface Log::debug('Found a journal_id trigger with 1 journal, true.'); $journalTrigger = true; } + if ('journal_id' === $triggerName && is_string($values) && !str_contains($values,',')) { + Log::debug('Found a journal_id trigger with 1 journal, true.'); + $journalTrigger = true; + } if (in_array($triggerName, ['date_is', 'date', 'on', 'date_before', 'before', 'date_after', 'after'], true)) { Log::debug('Found a date related trigger, set to true.'); $dateTrigger = true; } } - $result = $journalTrigger && $dateTrigger; + $result = $journalTrigger && $dateTrigger; Log::debug(sprintf('Result of hasSpecificJournalTrigger is %s.', var_export($result, true))); return $result; @@ -495,11 +496,11 @@ class SearchRuleEngine implements RuleEngineInterface if ($result) { $this->resultCount[$journalId] = array_key_exists($journalId, $this->resultCount) ? $this->resultCount[$journalId]++ : 1; Log::debug(sprintf( - 'Action "%s" on journal #%d was executed, so count a result. Updated transaction journal count is now %d.', - $ruleAction->action_type, - $transaction['transaction_journal_id'] ?? 0, - count($this->resultCount) - )); + 'Action "%s" on journal #%d was executed, so count a result. Updated transaction journal count is now %d.', + $ruleAction->action_type, + $transaction['transaction_journal_id'] ?? 0, + count($this->resultCount) + )); } if (false === $result) { Log::debug(sprintf('Action "%s" reports NO changes were made.', $ruleAction->action_type)); @@ -557,14 +558,14 @@ class SearchRuleEngine implements RuleEngineInterface $journalId = 0; foreach ($array as $triggerName => $values) { if ('journal_id' === $triggerName && is_array($values) && 1 === count($values)) { - $journalId = (int) trim($values[0] ?? '"0"', '"'); // follows format "123". + $journalId = (int)trim($values[0] ?? '"0"', '"'); // follows format "123". Log::debug(sprintf('Found journal ID #%d', $journalId)); } } if (0 !== $journalId) { $repository = app(JournalRepositoryInterface::class); $repository->setUser($this->user); - $journal = $repository->find($journalId); + $journal = $repository->find($journalId); if (null !== $journal) { $date = $journal->date; Log::debug(sprintf('Found journal #%d with date %s.', $journal->id, $journal->date->format('Y-m-d'))); @@ -576,4 +577,19 @@ class SearchRuleEngine implements RuleEngineInterface return today(config('app.timezone')); } + + + #[\Override] + public function removeOperator(string $type): void + { + $new = []; + foreach ($this->operators as $operator) { + if ($type === $operator['type']) { + Log::debug(sprintf('Removing operator "%s"', $type)); + continue; + } + $new[] = $operator; + } + $this->operators = $new; + } }