diff --git a/app/Helpers/Collector/Extensions/AccountCollection.php b/app/Helpers/Collector/Extensions/AccountCollection.php index 1de78d7f31..190e03f1eb 100644 --- a/app/Helpers/Collector/Extensions/AccountCollection.php +++ b/app/Helpers/Collector/Extensions/AccountCollection.php @@ -25,8 +25,11 @@ declare(strict_types=1); namespace FireflyIII\Helpers\Collector\Extensions; use FireflyIII\Helpers\Collector\GroupCollectorInterface; +use FireflyIII\Models\Account; +use FireflyIII\Support\Facades\Steam; use Illuminate\Database\Eloquent\Builder as EloquentBuilder; use Illuminate\Support\Collection; +use Illuminate\Support\Facades\Log; /** * Trait AccountCollection @@ -211,9 +214,9 @@ trait AccountCollection $this->query->leftJoin('account_types as source_account_type', 'source_account_type.id', '=', 'source_account.account_type_id'); // add source account fields: - $this->fields[] = 'source_account.name as source_account_name'; - $this->fields[] = 'source_account.iban as source_account_iban'; - $this->fields[] = 'source_account_type.type as source_account_type'; + $this->fields[] = 'source_account.name as source_account_name'; + $this->fields[] = 'source_account.iban as source_account_iban'; + $this->fields[] = 'source_account_type.type as source_account_type'; // same for dest $this->query->leftJoin('accounts as dest_account', 'dest_account.id', '=', 'destination.account_id'); @@ -228,4 +231,61 @@ trait AccountCollection return $this; } + + #[\Override] public function accountBalanceIs(string $direction, string $operator, string $value): GroupCollectorInterface + { + Log::warning(sprintf('GroupCollector will be SLOW: accountBalanceIs: "%s" "%s" "%s"', $direction, $operator, $value)); + + /** + * @param int $index + * @param array $object + * + * @return bool + */ + $filter = static function (array $object) use ($direction, $operator, $value): bool { + /** @var array $transaction */ + foreach ($object['transactions'] as $transaction) { + $key = sprintf('%s_account_id', $direction); + $accountId = $transaction[$key] ?? 0; + if (0 === $accountId) { + return false; + } + // in theory, this could lead to finding other users accounts. + $balance = Steam::finalAccountBalance(Account::find($accountId), $transaction['date']); + $result = bccomp($balance['balance'], $value); + Log::debug(sprintf('"%s" vs "%s" is %d', $balance['balance'], $value, $result)); + switch ($operator) { + default: + Log::error(sprintf('GroupCollector: accountBalanceIs: unknown operator "%s"', $operator)); + return false; + case '==': + Log::debug('Expect result to be 0 (equal)'); + return 0 === $result; + case '!=': + Log::debug('Expect result to be -1 or 1 (not equal)'); + return 0 !== $result; + case '>': + Log::debug('Expect result to be 1 (greater then)'); + return 1 === $result; + case '>=': + Log::debug('Expect result to be 0 or 1 (greater then or equal)'); + return -1 !== $result; + case '<': + Log::debug('Expect result to be -1 (less than)'); + return -1 === $result; + case '<=': + Log::debug('Expect result to be -1 or 0 (less than or equal)'); + return 1 !== $result; + } + //if($balance['balance'] $operator $value) { + + //} + } + + return false; + }; + $this->postFilters[] = $filter; + + return $this; + } } diff --git a/app/Helpers/Collector/GroupCollectorInterface.php b/app/Helpers/Collector/GroupCollectorInterface.php index 9171a65b44..8338565de4 100644 --- a/app/Helpers/Collector/GroupCollectorInterface.php +++ b/app/Helpers/Collector/GroupCollectorInterface.php @@ -732,4 +732,6 @@ interface GroupCollectorInterface public function yearIs(string $year): self; public function yearIsNot(string $year): self; + + public function accountBalanceIs(string $direction, string $operator, string $value): self; } diff --git a/app/Support/Search/OperatorQuerySearch.php b/app/Support/Search/OperatorQuerySearch.php index 48d0739a14..ae9ee4966a 100644 --- a/app/Support/Search/OperatorQuerySearch.php +++ b/app/Support/Search/OperatorQuerySearch.php @@ -1822,6 +1822,50 @@ class OperatorQuerySearch implements SearchInterface case 'sepa_ct_is': $this->collector->setSepaCT($value); + break; + case 'source_balance_gte': + case '-source_balance_lt': + $this->collector->accountBalanceIs('source', '>=', $value); + break; + case '-source_balance_gte': + case 'source_balance_lt': + $this->collector->accountBalanceIs('source', '<', $value); + break; + case 'source_balance_gt': + case '-source_balance_lte': + $this->collector->accountBalanceIs('source', '>', $value); + break; + case '-source_balance_gt': + case 'source_balance_lte': + $this->collector->accountBalanceIs('source', '<=', $value); + break; + case 'source_balance_is': + $this->collector->accountBalanceIs('source', '==', $value); + break; + case '-source_balance_is': + $this->collector->accountBalanceIs('source', '!=', $value); + break; + case 'destination_balance_gte': + case '-destination_balance_lt': + $this->collector->accountBalanceIs('destination', '>=', $value); + break; + case '-destination_balance_gte': + case 'destination_balance_lt': + $this->collector->accountBalanceIs('destination', '<', $value); + break; + case 'destination_balance_gt': + case '-destination_balance_lte': + $this->collector->accountBalanceIs('destination', '>', $value); + break; + case '-destination_balance_gt': + case 'destination_balance_lte': + $this->collector->accountBalanceIs('destination', '<=', $value); + break; + case 'destination_balance_is': + $this->collector->accountBalanceIs('destination', '==', $value); + break; + case '-destination_balance_is': + $this->collector->accountBalanceIs('destination', '!=', $value); break; } diff --git a/config/search.php b/config/search.php index 021da929f3..2fce64e4fd 100644 --- a/config/search.php +++ b/config/search.php @@ -240,5 +240,17 @@ return [ 'sepa_ct_is' => ['alias' => false, 'needs_context' => true], 'no_external_id' => ['alias' => false, 'needs_context' => false], 'any_external_id' => ['alias' => false, 'needs_context' => false], + + // based on source or destination balance. Very heavy search. + 'source_balance_gte' => ['alias' => false, 'needs_context' => true], + 'source_balance_gt' => ['alias' => false, 'needs_context' => true], + 'source_balance_lte' => ['alias' => false, 'needs_context' => true], + 'source_balance_lt' => ['alias' => false, 'needs_context' => true], + 'source_balance_is' => ['alias' => false, 'needs_context' => true], + 'destination_balance_gte' => ['alias' => false, 'needs_context' => true], + 'destination_balance_gt' => ['alias' => false, 'needs_context' => true], + 'destination_balance_lte' => ['alias' => false, 'needs_context' => true], + 'destination_balance_lt' => ['alias' => false, 'needs_context' => true], + 'destination_balance_is' => ['alias' => false, 'needs_context' => true], ], ]; diff --git a/resources/lang/en_US/firefly.php b/resources/lang/en_US/firefly.php index 05777916fd..abc8f677c3 100644 --- a/resources/lang/en_US/firefly.php +++ b/resources/lang/en_US/firefly.php @@ -699,6 +699,33 @@ return [ 'create_rule_from_query' => 'Create new rule from search query', 'rule_from_search_words' => 'The rule engine has a hard time handling ":string". The suggested rule that fits your search query may give different results. Please verify the rule triggers carefully.', + // more new stuff + 'search_modifier_destination_balance_is' => 'Destination account balance is exactly ":value"', + 'search_modifier_not_destination_balance_is' => 'Destination account balance is NOT exactly ":value"', + 'search_modifier_source_balance_is' => 'Source account balance is exactly ":value"', + 'search_modifier_not_source_balance_is' => 'Source account balance is NOT exactly ":value"', + + 'search_modifier_destination_balance_lte' => 'Destination account balance is less than or equal to ":value"', + 'search_modifier_not_destination_balance_lte' => 'Destination account balance is more than ":value"', + 'search_modifier_source_balance_lte' => 'Source account balance is less than or equal to ":value"', + 'search_modifier_not_source_balance_lte' => 'Source account balance is more than ":value"', + + 'search_modifier_destination_balance_lt' => 'Destination account balance is less than ":value"', + 'search_modifier_not_destination_balance_lt' => 'Destination account balance is more than or equal to ":value"', + 'search_modifier_source_balance_lt' => 'Source account balance is less than ":value"', + 'search_modifier_not_source_balance_lt' => 'Source account balance is more than or equal to ":value"', + + 'search_modifier_destination_balance_gte' => 'Destination account balance is more than or equal to ":value"', + 'search_modifier_not_destination_balance_gte' => 'Destination account balance is less than ":value"', + 'search_modifier_source_balance_gte' => 'Source account balance is more than or equal to ":value"', + 'search_modifier_not_source_balance_gte' => 'Source account balance is less than ":value"', + + 'search_modifier_destination_balance_gt' => 'Destination account balance is more than ":value"', + 'search_modifier_not_destination_balance_gt' => 'Destination account balance is less than or equal to ":value"', + 'search_modifier_source_balance_gt' => 'Source account balance is more than ":value"', + 'search_modifier_not_source_balance_gt' => 'Source account balance is less than or equal to ":value"', + + // Ignore this comment // END @@ -1196,6 +1223,44 @@ return [ 'rule_trigger_not_destination_is_cash' => 'Destination account is not a cash account', 'rule_trigger_not_account_is_cash' => 'Neither account is a cash account', + // new account balance things. + 'rule_trigger_destination_balance_gt_choice' => 'Destination account balance is more than..', + 'rule_trigger_destination_balance_gte_choice' => 'Destination account balance is more than or equal to..', + 'rule_trigger_destination_balance_is_choice' => 'Destination account balance is exactly..', + 'rule_trigger_destination_balance_lt_choice' => 'Destination account balance is less than..', + 'rule_trigger_destination_balance_lte_choice' => 'Destination account balance is less than or equal to..', + + 'rule_trigger_source_balance_gt_choice' => 'Source account balance is more than..', + 'rule_trigger_source_balance_gte_choice' => 'Source account balance is more than or equal to..', + 'rule_trigger_source_balance_is_choice' => 'Source account balance is exactly..', + 'rule_trigger_source_balance_lt_choice' => 'Source account balance is less than..', + 'rule_trigger_source_balance_lte_choice' => 'Source account balance is less than or equal to..', + + 'rule_trigger_destination_balance_gt' => 'Destination account balance is more than :trigger_value', + 'rule_trigger_destination_balance_gte' => 'Destination account balance is more than or equal to :trigger_value', + 'rule_trigger_destination_balance_is' => 'Destination account balance is exactly :trigger_value', + 'rule_trigger_destination_balance_lt' => 'Destination account balance is less than :trigger_value', + 'rule_trigger_destination_balance_lte' => 'Destination account balance is less than or equal to :trigger_value', + + 'rule_trigger_source_balance_gt' => 'Source account balance is more than :trigger_value', + 'rule_trigger_source_balance_gte' => 'Source account balance is more than or equal to :trigger_value', + 'rule_trigger_source_balance_is' => 'Source account balance is exactly :trigger_value', + 'rule_trigger_source_balance_lt' => 'Source account balance is less than :trigger_value', + 'rule_trigger_source_balance_lte' => 'Source account balance is less than or equal to :trigger_value', + + // NOT + 'rule_trigger_not_destination_balance_gt' => 'Destination account balance is less than or equal to :trigger_value', + 'rule_trigger_not_destination_balance_gte' => 'Destination account balance is less than :trigger_value', + 'rule_trigger_not_destination_balance_is' => 'Destination account balance is not :trigger_value', + 'rule_trigger_not_destination_balance_lt' => 'Destination account balance is more than or equal to :trigger_value', + 'rule_trigger_not_destination_balance_lte' => 'Destination account balance is more than :trigger_value', + + 'rule_trigger_not_source_balance_gt' => 'Source account balance is less than or equal to :trigger_value', + 'rule_trigger_not_source_balance_gte' => 'Source account balance is less than :trigger_value', + 'rule_trigger_not_source_balance_is' => 'Source account balance is not :trigger_value', + 'rule_trigger_not_source_balance_lt' => 'Source account balance is greater than or equal to :trigger_value', + 'rule_trigger_not_source_balance_lte' => 'Source account balance is greater than :trigger_value', + // Ignore this comment // actions